/*
* 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.util;
import java.util.Arrays;
import java.util.HashMap;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.ValueFormatException;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.version.VersionException;
import javax.security.auth.Subject;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.betaconceptframework.astroboa.api.model.CmsApiConstants;
import org.betaconceptframework.astroboa.api.model.CmsRepositoryEntity;
import org.betaconceptframework.astroboa.api.model.ContentObject;
import org.betaconceptframework.astroboa.api.model.RepositoryUser;
import org.betaconceptframework.astroboa.api.model.StringProperty;
import org.betaconceptframework.astroboa.api.model.ValueType;
import org.betaconceptframework.astroboa.api.model.definition.ContentObjectTypeDefinition;
import org.betaconceptframework.astroboa.api.model.exception.CmsException;
import org.betaconceptframework.astroboa.api.model.exception.CmsNonUniqueContentObjectSystemNameException;
import org.betaconceptframework.astroboa.api.model.query.criteria.ContentObjectCriteria;
import org.betaconceptframework.astroboa.api.security.RepositoryUserIdPrincipal;
import org.betaconceptframework.astroboa.context.AstroboaClientContextHolder;
import org.betaconceptframework.astroboa.context.SecurityContext;
import org.betaconceptframework.astroboa.engine.database.dao.CmsRepositoryEntityAssociationDao;
import org.betaconceptframework.astroboa.engine.jcr.PrototypeFactory;
import org.betaconceptframework.astroboa.engine.jcr.dao.RepositoryUserDao;
import org.betaconceptframework.astroboa.engine.jcr.query.CmsQueryHandler;
import org.betaconceptframework.astroboa.engine.jcr.query.CmsQueryResult;
import org.betaconceptframework.astroboa.model.factory.CmsCriteriaFactory;
import org.betaconceptframework.astroboa.model.impl.LazyCmsProperty;
import org.betaconceptframework.astroboa.model.impl.SaveMode;
import org.betaconceptframework.astroboa.model.impl.item.CmsBuiltInItem;
import org.betaconceptframework.astroboa.util.CmsConstants;
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 PopulateContentObject {
@Autowired
private PrototypeFactory prototypeFactory;
@Autowired
private CmsRepositoryEntityAssociationDao cmsRepositoryEntityAssociationDao;
@Autowired
private CmsQueryHandler cmsQueryHandler;
@Autowired
private RepositoryUserDao repositoryUserDao;
private Node contentObjectNode;
private ContentObject contentObject;
private Session session;
private SaveMode saveMode;
private Context context;
private final Logger logger = LoggerFactory.getLogger(getClass());
public void setContext(Context context) {
this.context = context;
}
public void setSaveMode(SaveMode saveMode) {
this.saveMode = saveMode;
}
public void setContentObjectNode(Node contentObjectNode) {
this.contentObjectNode = contentObjectNode;
}
public void setContentObject(ContentObject contentObject) {
this.contentObject = contentObject;
}
public void setSession(Session session) {
this.session = session;
}
public void populate(ContentObjectTypeDefinition contentObjectTypeDefinition) {
try{
//Check that type is the same, if any
if (contentObjectNode.hasProperty(CmsBuiltInItem.ContentObjectTypeName.getJcrName())){
String contentObjectTypeInJcrNode = contentObjectNode.getProperty(CmsBuiltInItem.ContentObjectTypeName.getJcrName()).getString();
if (!contentObjectTypeInJcrNode.equals(contentObject.getContentObjectType()))
throw new CmsException("Content object's jcr node "+ contentObjectNode.getPath() +" is of type "+ contentObjectTypeInJcrNode +
" where as content object is fo type "+contentObject.getContentObjectType());
}
else{
//Set content object's type
contentObjectNode.setProperty(CmsBuiltInItem.ContentObjectTypeName.getJcrName(), contentObjectTypeDefinition.getName());
}
populateSystemName();
//Set content object's owner
populateOwner();
//Populate Child Properties
PopulateComplexCmsProperty contentObjectPropertyContainerPopulateTask = prototypeFactory.newPopulateComplexCmsProperty();
contentObjectPropertyContainerPopulateTask.setComplexProperty(contentObject.getComplexCmsRootProperty());
contentObjectPropertyContainerPopulateTask.setComplexPropertyDefinition(contentObject.getComplexCmsRootProperty().getPropertyDefinition());
contentObjectPropertyContainerPopulateTask.setSaveMode(saveMode);
contentObjectPropertyContainerPopulateTask.setSession(session);
contentObjectPropertyContainerPopulateTask.setComplexPropertyNode(contentObjectNode);
contentObjectPropertyContainerPopulateTask.setContentObjectNodeUUID(contentObjectNode.getIdentifier());
contentObjectPropertyContainerPopulateTask.setContext(context);
contentObjectPropertyContainerPopulateTask.populate();
if ( StringUtils.isBlank(((LazyCmsProperty) contentObject.getComplexCmsRootProperty()).getContentObjectNodeUUID())){
((LazyCmsProperty) contentObject.getComplexCmsRootProperty()).setContentObjectNodeUUID(contentObjectNode.getIdentifier());
}
if ( StringUtils.isBlank(((LazyCmsProperty) contentObject.getComplexCmsRootProperty()).getPropertyContainerUUID())){
((LazyCmsProperty) contentObject.getComplexCmsRootProperty()).setPropertyContainerNodeUUID(contentObjectNode.getIdentifier());
}
}
catch(CmsException e){
throw e;
}
catch (Exception e){
throw new CmsException(e);
}
}
private void populateSystemName() throws ValueFormatException,
VersionException, LockException, ConstraintViolationException,
RepositoryException {
//Validate system name first
String systemName = contentObject.getSystemName();
//System name is NULL or an empty string
if (StringUtils.isBlank(systemName)){
// User did not specify a system name. Do not create one if object already has one
if (systemName == null){
//If object already has a system name, retrieve value from the repository and update object
if (contentObjectNode.hasProperty(CmsBuiltInItem.SystemName.getJcrName())){
//Update object with existing system name and return
systemName = contentObjectNode.getProperty(CmsBuiltInItem.SystemName.getJcrName()).getString();
//Maybe unnecessary check.
if (! context.getCmsRepositoryEntityUtils().isValidSystemName(systemName)){
throw new RepositoryException("Existing Object system name "+systemName+" is not valid. It should match pattern "+CmsConstants.SYSTEM_NAME_REG_EXP);
}
contentObject.setSystemName(systemName);
return;
}
}
//System name is empty. This way user specifies that she wants a new system name to be created
//Generate a system name
//1. Check profile.title property from the object and if none if found, check
//if title
systemName = retrieveContentObjectProfileTitle();
if (StringUtils.isBlank(systemName)){
//No profile.title found. Even though this property is considered
//mandatory and normally this should never happen
//system name must not be blank. Therefore we create
//a value using path to content object node plus its UUID
systemName = JcrNodeUtils.getYearMonthDayPathForContentObjectNode(contentObjectNode)+contentObjectNode.getIdentifier();
}
//Filter created system name
//Replace anything that is not valid according to SystemName regular expression
systemName = context.getCmsRepositoryEntityUtils().fixSystemName(systemName);
contentObject.setSystemName(systemName);
}
if (! context.getCmsRepositoryEntityUtils().isValidSystemName(systemName)){
throw new RepositoryException("Content Object system name "+systemName+" is not valid. It should match pattern "+CmsConstants.SYSTEM_NAME_REG_EXP);
}
//Finally check that system name is unique across all content objects
if (foundAtLeastOneMoreContentObjectWithSameSystemName(systemName)){
//One or more content objects were found with the same systemName.
//SystemName will be the same with content object id plus some characters from title
systemName = context.getCmsRepositoryEntityUtils().fixSystemName(retrieveContentObjectProfileTitle()+"-"+contentObject.getId());
//Now run again query with new value
if (foundAtLeastOneMoreContentObjectWithSameSystemName(systemName)){
throw new CmsNonUniqueContentObjectSystemNameException("Another content object exists with system name "+ systemName);
}
else{
logger.warn("ContentObject {} was saved with system name {} because provided systemName {} was found in another content object",
new Object[]{contentObject.getId(), systemName, contentObject.getSystemName()});
contentObject.setSystemName(systemName);
}
}
JcrNodeUtils.addSimpleProperty(SaveMode.UPDATE, contentObjectNode,
CmsBuiltInItem.SystemName, systemName, session.getValueFactory(), ValueType.String);
}
private boolean foundAtLeastOneMoreContentObjectWithSameSystemName(String systemName) throws RepositoryException{
/*
* Criterion equalsIgnoreCaseCriterion = CriterionFactory.equalsCaseInsensitive(CmsBuiltInItem.SystemName.getJcrName(), systemName.toLowerCase());
* Criterion likeIgnoreCaseCriterion = CriterionFactory.likeCaseInsensitive(CmsBuiltInItem.SystemName.getJcrName(), systemName.toLowerCase());
* coCriteria.addCriterion(CriterionFactory.or(equalsIgnoreCaseCriterion, likeIgnoreCaseCriterion));
*/
ContentObjectCriteria coCriteria = CmsCriteriaFactory.newContentObjectCriteria();
coCriteria.addSystemNameEqualsCriterionIgnoreCase(systemName);
if (contentObject.getId() != null){
coCriteria.addIdNotEqualsCriterion(contentObject.getId());
}
//We are only interested in result count
coCriteria.setOffsetAndLimit(0, 0);
CmsQueryResult nodesWithSameSystemName = cmsQueryHandler.getNodesFromXPathQuery(session, coCriteria);
return nodesWithSameSystemName != null && nodesWithSameSystemName.getTotalRowCount() > 0;
}
private String retrieveContentObjectProfileTitle()
throws RepositoryException, ValueFormatException,
PathNotFoundException
{
if (contentObject.getComplexCmsRootProperty().isChildPropertyLoaded("profile.title")
&& contentObject.getCmsProperty("profile.title") != null &&
((StringProperty)contentObject.getCmsProperty("profile.title")).hasValues())
{
return ((StringProperty)contentObject.getCmsProperty("profile.title")).getSimpleTypeValue();
}
else
{
//Profile title has not been loaded. Check jcr node
if (contentObjectNode.hasProperty("profile/title"))
{
return contentObjectNode.getProperty("profile/title").getString();
}
}
return "";
}
private void populateOwner() throws RepositoryException{
RepositoryUser owner = contentObject.getOwner();
if (owner == null){
owner = retrieveRepositoryUserForActiveClient();
//If a new content object is saved then load RepositoryUser which represents
//the connected user, to be the owner of the object. If no such repository user is found
//then throw an exception
if (SaveMode.INSERT == saveMode){
contentObject.setOwner(owner);
}
//Special case. In case of an update check if existing owner is the same
//with the user performing the update. If so do not throw an exception
else {
String existingOwnerId = null;
if (contentObjectNode.hasProperty(CmsBuiltInItem.OwnerCmsIdentifier.getJcrName())){
existingOwnerId = contentObjectNode.getProperty(CmsBuiltInItem.OwnerCmsIdentifier.getJcrName()).getString();
}
if (StringUtils.equals(owner.getId(), existingOwnerId)){
contentObject.setOwner(owner);
//In this case there is nothing else to be done.
return;
}
else{
if (existingOwnerId == null){
//This should never happen
logger.warn("Object "+context.getCmsRepositoryEntityUtils().nodeIdentity(contentObjectNode) + " does not have an owner. SYSTEM will be the owner");
owner = repositoryUserDao.getSystemRepositoryUser();
contentObject.setOwner(owner);
}
else{
//No owner has been provided and active user is not the owner of the object.
//However, active user has the permissions to update the object, therefore we allow save
//and we set the proper owner instance
Node repUsernode = context.retrieveNodeForRepositoryUser(existingOwnerId);
owner = repositoryUserDao.renderRepositoryUserFromNode(repUsernode, session, null, new HashMap<String, CmsRepositoryEntity>());
contentObject.setOwner(owner);
//In this case there is nothing else to be done.
return;
}
}
}
}
//First of all we try to locate owner id;
//Owner must exist in repository
if (owner.getId() == null){
if (StringUtils.equals(CmsApiConstants.SYSTEM_REPOSITORY_USER_EXTRENAL_ID, owner.getExternalId())){
//Owner is SYSTEM user. Retrieve property identifier
RepositoryUser systemUser = repositoryUserDao.getSystemRepositoryUser();
if (!StringUtils.equals(systemUser.getId(), owner.getId())){
owner = systemUser;
}
}
else if (StringUtils.isBlank(owner.getExternalId())){
throw new RepositoryException("Content Object Owner has no id nor external id");
}
else{
//Search in cache
Node userNode = context.getNodeFromCache(owner.getExternalId());
if (userNode == null){
//try to locate using search criteria
RepositoryUser repUser = repositoryUserDao.getRepositoryUser(owner.getExternalId());
if (repUser == null){
throw new RepositoryException("Content Object Owner with external id "+ owner.getExternalId() + " does not exist");
}
else{
owner = repUser;
//Cache node
context.retrieveNodeForRepositoryUser(repUser.getId());
}
}
else{
owner.setId(context.getCmsRepositoryEntityUtils().getCmsIdentifier(userNode));
}
}
}
String newOwnerCmsIdentifier = owner.getId();
String existingOwnerId = null;
if (contentObjectNode.hasProperty(CmsBuiltInItem.OwnerCmsIdentifier.getJcrName())){
existingOwnerId = contentObjectNode.getProperty(CmsBuiltInItem.OwnerCmsIdentifier.getJcrName()).getString();
}
//Update owner only if value does not exist or it is different that the one provided
if (! StringUtils.equals(newOwnerCmsIdentifier, existingOwnerId)){
//Check that new owner does exist
if (context.retrieveNodeForRepositoryUser(newOwnerCmsIdentifier) == null){
//Special case it may be user SYSTEM. in that case we do not throw any exception
if (StringUtils.equals(CmsApiConstants.SYSTEM_REPOSITORY_USER_EXTRENAL_ID, owner.getExternalId())){
owner = repositoryUserDao.getSystemRepositoryUser();
//if ids match do not continue
if (StringUtils.equals(owner.getId(), existingOwnerId)){
return ;
}
}
else{
throw new CmsException("RepositoryUser with id "+ newOwnerCmsIdentifier + " and externalId "+ owner.getExternalId() + " and label "+
owner.getLabel() + " does not exist in repository "+ AstroboaClientContextHolder.getActiveRepositoryId());
}
}
EntityAssociationUpdateHelper<RepositoryUser> entityAssociationUpdateHelper =
new EntityAssociationUpdateHelper<RepositoryUser>(session, cmsRepositoryEntityAssociationDao, context);
entityAssociationUpdateHelper.setReferrerCmsRepositoryEntityNode(contentObjectNode);
entityAssociationUpdateHelper.setReferrerPropertyName(CmsBuiltInItem.OwnerCmsIdentifier);
entityAssociationUpdateHelper.setReferrerPropertyNameMultivalue(false);
entityAssociationUpdateHelper.setValuesToBeAdded(Arrays.asList(owner));
entityAssociationUpdateHelper.update();
}
}
private RepositoryUser retrieveRepositoryUserForActiveClient() throws RepositoryException{
SecurityContext securityContext = AstroboaClientContextHolder.getActiveSecurityContext();
String repositoryUserId = null;
String activeUsername = null;
if (securityContext != null){
Subject subject = securityContext.getSubject();
if (subject != null && CollectionUtils.isNotEmpty(subject.getPrincipals(RepositoryUserIdPrincipal.class))){
repositoryUserId = subject.getPrincipals(RepositoryUserIdPrincipal.class).iterator().next().getName();
Node repositoryUserNode = context.retrieveNodeForRepositoryUser(repositoryUserId);
return repositoryUserDao.renderRepositoryUserFromNode(repositoryUserNode, session, null, null);
}
else{
//Could not locate repository user id. Try with user name
activeUsername = securityContext.getIdentity();
return repositoryUserDao.getRepositoryUser(activeUsername);
}
}
throw new CmsException("Could not locate a RepositoryUser neither with identifier "+repositoryUserId+" nor with externalId "+activeUsername);
}
}