/*
* 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.HashMap;
import java.util.List;
import java.util.Map;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.betaconceptframework.astroboa.api.model.CmsRepositoryEntity;
import org.betaconceptframework.astroboa.api.model.Topic;
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.CmsOutcome;
import org.betaconceptframework.astroboa.api.model.query.criteria.TopicCriteria;
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.io.SerializationBean.CmsEntityType;
import org.betaconceptframework.astroboa.engine.jcr.query.CmsQueryHandler;
import org.betaconceptframework.astroboa.engine.jcr.query.CmsQueryResult;
import org.betaconceptframework.astroboa.engine.jcr.renderer.TopicRenderer;
import org.betaconceptframework.astroboa.engine.jcr.util.CmsRepositoryEntityUtils;
import org.betaconceptframework.astroboa.engine.jcr.util.Context;
import org.betaconceptframework.astroboa.engine.jcr.util.RendererUtils;
import org.betaconceptframework.astroboa.engine.jcr.util.TopicUtils;
import org.betaconceptframework.astroboa.model.factory.CmsCriteriaFactory;
import org.betaconceptframework.astroboa.model.impl.SaveMode;
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.springframework.beans.factory.annotation.Autowired;
/**
*
* @author Gregory Chomatas (gchomatas@betaconcept.com)
* @author Savvas Triantafyllou (striantafyllou@betaconcept.com)
*
*/
public class TopicDao extends JcrDaoSupport {
@Autowired
private CmsRepositoryEntityAssociationDao cmsRepositoryEntityAssociationDao;
@Autowired
private CmsRepositoryEntityUtils cmsRepositoryEntityUtils;
@Autowired
private TopicRenderer topicRenderer;
@Autowired
private CmsQueryHandler cmsQueryHandler;
@Autowired
private TopicUtils topicUtils;
@Autowired
private JcrQueryCacheRegion jcrQueryCacheRegion;
@Autowired
private RendererUtils rendererUtils;
@Autowired
private SerializationDao serializationDao;
@Autowired
private ImportDao importDao;
public boolean deleteTopicTree(String topicIdOrName) {
if (StringUtils.isBlank(topicIdOrName))
throw new CmsException("Blank (empty or null) topic id. Could not delete topic tree. ");
Context context = null;
try {
Session session = getSession();
Node topicNode = getTopicNodeByIdOrName(topicIdOrName);
if (topicNode == null){
logger.info("Topic {} does not exist and therefore cannot be deleted", topicIdOrName);
return false;
}
context = new Context(cmsRepositoryEntityUtils, cmsQueryHandler, session);
removeTopicTree(topicNode, context);
session.save();
return true;
//jcrQueryCacheRegion.removeRegion();
}
catch(CmsException e){
throw e;
}
catch (Exception e) {
throw new CmsException(e);
}
finally{
if (context != null){
context.dispose();
context = null;
}
}
}
public CmsOutcome<Topic> getMostlyUsedTopics(String taxonomy, int offset, int limit) {
try{
List<String> mostlyUsedTopicIds = cmsRepositoryEntityAssociationDao.getReferrerCmsRepositoryEntityIdsOfAllAssociationsOfReferencedTaxonomyNodeOfTaxonomy(taxonomy);
Session session = getSession();
RenderProperties topicRenderProperties = newRenderProperties(null);
CmsOutcome<Topic> outcome = new CmsOutcomeImpl<Topic>(mostlyUsedTopicIds.size(), offset, limit);
//Render only specified range
Map<String, CmsRepositoryEntity> cachedCmsRepositoryEntities = new HashMap<String, CmsRepositoryEntity>();
if (offset < 0)
offset = 0;
if (limit < 0)
limit = mostlyUsedTopicIds.size();
for (int i=offset; i< limit && i < mostlyUsedTopicIds.size(); i++ )
{
Node topicNode = cmsRepositoryEntityUtils.retrieveUniqueNodeForTopic(session, mostlyUsedTopicIds.get(i));
renderTopicNode(session, outcome, topicRenderer,
cachedCmsRepositoryEntities, topicRenderProperties,
topicNode);
}
return outcome;
}
catch(CmsException e){
throw e;
}
catch (Exception e) {
throw new CmsException(e);
}
}
private RenderProperties newRenderProperties(String locale) {
RenderProperties topicRenderProperties = new RenderPropertiesImpl();
//topicRenderProperties.renderValuesForLocale(locale);
return topicRenderProperties;
}
public Topic getTopic(String topicId, String locale) {
if (StringUtils.isBlank(topicId))
throw new CmsException("Found no topic to render");
try {
Session session = getSession();
//Retrieve topic
Node topicNode = cmsRepositoryEntityUtils.retrieveUniqueNodeForTopic(session, topicId);
RenderProperties renderProperties = newRenderProperties(locale);
return topicRenderer.renderTopicAndItsParent(topicNode, renderProperties, session,
cmsRepositoryEntityFactoryForActiveClient.newTopic(), new HashMap<String, CmsRepositoryEntity>());
}
catch(CmsException e){
throw e;
}
catch (Exception e) {
throw new CmsException(e);
}
}
public Topic saveTopic(Object topicSource, Context context) throws CmsException {
if (topicSource == null){
throw new CmsException("Cannot save an empty Topic !");
}
if (topicSource instanceof String){
//Use importer to unmarshal String to Topic
//and to save it as well.
//What is happened is that importDao will create a Topic
//and will pass it here again to save it.
ImportConfiguration configuration = ImportConfiguration.topic()
.persist(PersistMode.PERSIST_MAIN_ENTITY)
.build();
return importDao.importTopic((String)topicSource, configuration);
}
if (! (topicSource instanceof Topic)){
throw new CmsException("Expecting either String or Topic and not "+topicSource.getClass().getName());
}
Topic topic = (Topic) topicSource;
SaveMode saveMode = null;
try {
//Determine SaveMode
saveMode = cmsRepositoryEntityUtils.determineSaveMode(topic);
Session session = getSession();
if (context == null){
context = new Context(cmsRepositoryEntityUtils, cmsQueryHandler, session);
}
Node topicNode = null;
//Save Topic
switch (saveMode) {
case UPDATE:
topicNode = topicUtils.updateTopic(session, topic, null, context);
break;
case INSERT:
topicNode = insertTopicNode(session, topic, context);
break;
default:
break;
}
session.save();
if (topicNode != null){
context.cacheTopicNode(topicNode, true);
}
//jcrQueryCacheRegion.removeRegion();
return topic;
}
catch(CmsException e){
throw e;
}
catch (Exception e) {
throw new CmsException(e);
}
finally{
if (context != null){
context.dispose();
context = null;
}
}
}
private CmsOutcome<Topic> createTopicOutcome(Session session, TopicCriteria topicCriteria) throws Exception {
RenderProperties renderPropertiesForCache = rendererUtils.copyRenderPropertiesFromCriteria(topicCriteria);
//CmsQueryResultSecurityHandler cmsQueryResultSecurityHandler = new CmsQueryResultSecurityHandler(cmsQueryHandler,
// topicCriteria,session, accessManager);
CmsQueryResult cmsQueryResult = cmsQueryHandler.getNodesFromXPathQuery(session, topicCriteria);
CmsOutcome<Topic> outcome = new CmsOutcomeImpl<Topic>(cmsQueryResult.getTotalRowCount(), topicCriteria.getOffset(), topicCriteria.getLimit());
if (topicCriteria.getLimit() != 0){
Map<String, CmsRepositoryEntity> cachedCmsRepositoryEntities = new HashMap<String, CmsRepositoryEntity>();
RenderProperties topicRenderProperties = topicCriteria.getRenderProperties();
NodeIterator nodeIterator = cmsQueryResult.getNodeIterator();
while (nodeIterator.hasNext())
{
Node nextNode = nodeIterator.nextNode();
renderTopicNode(session, outcome, topicRenderer,
cachedCmsRepositoryEntities, topicRenderProperties,
nextNode);
}
}
//((CmsOutcomeImpl<Topic>)outcome).setCount(nodeIterator.getSize());
//Cache results
if (topicCriteria.isCacheable()){
String xpathQuery = topicCriteria.getXPathQuery();
if (!StringUtils.isBlank(xpathQuery) && outcome != null && outcome.getCount() > 0){
jcrQueryCacheRegion.cacheJcrQueryResults(topicCriteria,
outcome, renderPropertiesForCache);
}
}
return outcome;
}
private void renderTopicNode(Session session, CmsOutcome<Topic> outcome,
TopicRenderer topicRenderer,
Map<String, CmsRepositoryEntity> cachedCmsRepositoryEntities,
RenderProperties topicRenderProperties, Node topicNode)
throws RepositoryException {
Topic renderRow = topicRenderer.renderNode(session, topicNode, topicRenderProperties, cachedCmsRepositoryEntities);
outcome.getResults().add(renderRow);
}
public Node insertTopicNode(Session session, Topic topic, Context context) throws RepositoryException {
//Set Default taxonomy if none exists
setDefaultTaxonomyIfNoneIsProvided(topic);
Node parentTopicNode = topicUtils.retrieveParentTopicNode(session, topic, context);
return topicUtils.addNewTopicJcrNode(parentTopicNode, topic, session, false, context);
}
private void setDefaultTaxonomyIfNoneIsProvided(Topic topic) {
if (topic.getTaxonomy() == null){
if (topic.getParent()!= null && topic.getParent().getTaxonomy()!=null){
topic.setTaxonomy(topic.getParent().getTaxonomy());
}
else{
cmsRepositoryEntityUtils.addDefaultTaxonomyToTopic(topic);
}
}
}
public void removeTopicTree(Node topicNode, Context context) throws RepositoryException{
topicUtils.removeTopicJcrNode(topicNode, getSession(), true, context);
}
public Topic saveTopicTree(Topic topicToBeSaved, Context context){
if (context == null){
context = new Context(cmsRepositoryEntityUtils, cmsQueryHandler, getSession());
}
//Save topic
topicToBeSaved = saveTopic(topicToBeSaved, context);
//Save its children
if (topicToBeSaved.isChildrenLoaded()){
List<Topic> children = topicToBeSaved.getChildren();
if (CollectionUtils.isNotEmpty(children)){
for (Topic childTopic : children){
saveTopicTree(childTopic, context);
}
}
}
return topicToBeSaved;
}
public List<String> getContentObjectIdsWhichReferToTopic(String topicId) {
try {
logger.debug("Lazy load referrer content objects for topic {}",topicId);
return cmsRepositoryEntityAssociationDao.getReferrerCmsRepositoryEntityIdsOfAllAssociationsOfReferencedEntity(topicId, Topic.class);
}
catch(CmsException e){
throw e;
}
catch (Exception e) {
throw new CmsException(e);
}
}
public int getCountOfContentObjectIdsWhichReferToTopic(String topicId) {
try {
logger.debug("lazy load numnber of referrer content objects for topic {}",topicId);
return cmsRepositoryEntityAssociationDao.getCountOfReferrerCmsRepositoryEntityIdsOfAllAssociationsOfReferencedEntity(topicId, Topic.class);
}
catch(CmsException e){
throw e;
}
catch (Exception e) {
throw new CmsException(e);
}
}
@SuppressWarnings("unchecked")
public <T> T getTopic(String topicIdOrName, ResourceRepresentationType<T> topicOutput, FetchLevel fetchLevel, boolean prettyPrint) {
ByteArrayOutputStream os = null;
try {
Session session = getSession();
Node topicNode = getTopicNodeByIdOrName(topicIdOrName);
if (topicNode == null){
if (topicOutput != null && ResourceRepresentationType.TOPIC_LIST.equals(topicOutput)){
return (T) new CmsOutcomeImpl<Topic>(0, 0, 0);
}
return null;
}
if (topicOutput == null || ResourceRepresentationType.TOPIC_INSTANCE.equals(topicOutput)||
ResourceRepresentationType.TOPIC_LIST.equals(topicOutput)){
RenderProperties renderProperties = new RenderPropertiesImpl();
Topic topic = topicRenderer.renderTopicAndItsParent(topicNode, renderProperties, session,
cmsRepositoryEntityFactoryForActiveClient.newTopic(), new HashMap<String, CmsRepositoryEntity>());
//Pre fetch children or tree
if (fetchLevel != null){
switch (fetchLevel) {
case ENTITY_AND_CHILDREN:
//This way lazy loading will be enabled
topic.getChildren();
break;
case FULL:
loadAllChildren(topic.getChildren());
break;
default:
break;
}
}
//Return appropriate type
if (topicOutput == null || ResourceRepresentationType.TOPIC_INSTANCE.equals(topicOutput )){
return (T) topic;
}
else{
//Return type is CmsOutcome.
CmsOutcome<Topic> outcome = new CmsOutcomeImpl<Topic>(1, 0, 1);
outcome.getResults().add(topic);
return (T) outcome;
}
}
else if (ResourceRepresentationType.XML.equals(topicOutput)||
ResourceRepresentationType.JSON.equals(topicOutput)){
String topic = null;
os = new ByteArrayOutputStream();
SerializationConfiguration serializationConfiguration = SerializationConfiguration.topic()
.prettyPrint(prettyPrint)
.representationType(topicOutput)
.build();
serializationDao.serializeCmsRepositoryEntity(topicNode, os, CmsEntityType.TOPIC, null, fetchLevel, true, serializationConfiguration);
topic = new String(os.toByteArray(), "UTF-8");
return (T) topic;
}
else{
throw new CmsException("Invalid resource representation type for topic "+topicOutput);
}
}catch(RepositoryException ex){
throw new CmsException(ex);
}
catch(CmsException e){
throw e;
}
catch (Exception e) {
throw new CmsException(e);
}
finally{
IOUtils.closeQuietly(os);
}
}
public Node getTopicNodeByIdOrName(String topicIdOrName){
try{
Node topicNode = null;
if (StringUtils.isBlank(topicIdOrName)){
return null;
}
else if (CmsConstants.UUIDPattern.matcher(topicIdOrName).matches()){
topicNode = cmsRepositoryEntityUtils.retrieveUniqueNodeForTopic(getSession(), topicIdOrName);
if (topicNode != null){
return topicNode;
}
}
else{
TopicCriteria topicCriteria = CmsCriteriaFactory.newTopicCriteria();
topicCriteria.addNameEqualsCriterion(topicIdOrName);
topicCriteria.setOffsetAndLimit(0, 1);
CmsQueryResult nodes = cmsQueryHandler.getNodesFromXPathQuery(getSession(), topicCriteria);
if (nodes.getTotalRowCount() > 0){
return nodes.getNodeIterator().nextNode();
}
}
return null;
}
catch(CmsException e){
throw e;
}
catch (Exception e) {
throw new CmsException(e);
}
}
private void loadAllChildren(List<Topic> children) {
if (children != null && ! children.isEmpty()){
for (Topic child : children){
loadAllChildren(child.getChildren());
}
}
}
private <T> T returnEmptyResultSet(
ResourceRepresentationType<T> topicOutput) {
if (topicOutput.equals(ResourceRepresentationType.TOPIC_LIST)){
return (T) new CmsOutcomeImpl<T>(0, 0, 0);
}
else{
return null;
}
}
private Topic returnSingleTopicFromOutcome(TopicCriteria topicCriteria,
CmsOutcome<Topic> 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() +" topics matched criteria, user has specified limit "+
topicCriteria.getLimit() + " but she also requested return type to be a single Topic.");
}
else{
if (CollectionUtils.isNotEmpty(outcome.getResults())){
return outcome.getResults().get(0);
}
else {
return null;
}
}
}
}
public <T> T searchTopics(TopicCriteria topicCriteria, ResourceRepresentationType<T> topicOutput){
T queryResult = null;
boolean queryReturnedAtLeastOneResult = false;
ByteArrayOutputStream os = null;
try {
//Check if criteria is provided
if (topicCriteria == null){
return returnEmptyResultSet(topicOutput);
}
//Initialize null parameters (if any)
if (topicOutput == null){
topicOutput = (ResourceRepresentationType<T>) ResourceRepresentationType.TOPIC_LIST;
}
//Check cache
if (topicCriteria.isCacheable()){
queryResult = (T)jcrQueryCacheRegion.getJcrQueryResults(topicCriteria, topicOutput.getTypeAsString());
if (queryResult != null){
return queryResult;
}
}
//User requested Objects as return type
if (ResourceRepresentationType.TOPIC_INSTANCE.equals(topicOutput)||
ResourceRepresentationType.TOPIC_LIST.equals(topicOutput)){
CmsOutcome<Topic> outcome = createTopicOutcome(getSession(), topicCriteria);
//User requested one Topic. Throw an exception if more than
//one returned
if (ResourceRepresentationType.TOPIC_INSTANCE.equals(topicOutput )){
queryResult = (T) returnSingleTopicFromOutcome(topicCriteria, outcome);
queryReturnedAtLeastOneResult = queryResult != null;
}
else{
//Return type is CmsOutcome.
queryResult = (T) outcome;
queryReturnedAtLeastOneResult = outcome.getCount() > 0;
}
}
else if (ResourceRepresentationType.XML.equals(topicOutput)||
ResourceRepresentationType.JSON.equals(topicOutput)){
//User requested output to be XML or JSON
os = new ByteArrayOutputStream();
SerializationConfiguration serializationConfiguration = SerializationConfiguration.topic()
.prettyPrint(topicCriteria.getRenderProperties().isPrettyPrintEnabled())
.representationType(topicOutput)
.build();
long numberOfResutls = serializationDao.serializeSearchResults(getSession(), topicCriteria, os, FetchLevel.ENTITY, serializationConfiguration);
queryReturnedAtLeastOneResult = numberOfResutls > 0;
queryResult = (T) new String(os.toByteArray(), "UTF-8");
}
else{
throw new CmsException("Invalid resource representation type for topic search results "+topicOutput);
}
if (topicCriteria.isCacheable()){
String xpathQuery = topicCriteria.getXPathQuery();
if (!StringUtils.isBlank(xpathQuery) && queryReturnedAtLeastOneResult){
jcrQueryCacheRegion.cacheJcrQueryResults(topicCriteria,
queryResult, topicCriteria.getRenderProperties(), topicOutput.getTypeAsString());
}
}
return queryResult;
}
catch(RepositoryException ex){
throw new CmsException(ex);
}
catch(CmsException e){
throw e;
}
catch (Exception e) {
throw new CmsException(e);
}
finally{
IOUtils.closeQuietly(os);
}
}
}