/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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.
*
* Alfresco 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 Alfresco. If not, see .
*/
package org.alfresco.repo.publishing;
import static org.alfresco.repo.publishing.PublishingModel.ASPECT_PUBLISHED;
import static org.alfresco.repo.publishing.PublishingModel.ASSOC_LAST_PUBLISHING_EVENT;
import static org.alfresco.repo.publishing.PublishingModel.NAMESPACE;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.node.NodeUtils;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.publishing.NodeSnapshot;
import org.alfresco.service.cmr.publishing.PublishingEvent;
import org.alfresco.service.cmr.publishing.PublishingPackageEntry;
import org.alfresco.service.cmr.publishing.channels.Channel;
import org.alfresco.service.cmr.publishing.channels.ChannelType;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.util.GUID;
import org.alfresco.util.ParameterCheck;
/**
* @author Brian
* @author Nick Smith
* @since 4.0
*/
public class ChannelImpl implements Channel
{
private static final String PERMISSIONS_ERR_ACCESS_DENIED = "permissions.err_access_denied";
private final NodeRef nodeRef;
private final AbstractChannelType channelType;
private final String name;
private final ChannelHelper channelHelper;
private final NodeService nodeService;
private final DictionaryService dictionaryService;
private final PublishingEventHelper eventHelper;
public ChannelImpl(ServiceRegistry serviceRegistry, AbstractChannelType channelType, NodeRef nodeRef, String name,
ChannelHelper channelHelper, PublishingEventHelper eventHelper)
{
this.nodeRef = nodeRef;
this.channelType = channelType;
this.name = name;
this.channelHelper = channelHelper;
this.nodeService = serviceRegistry.getNodeService();
this.dictionaryService = serviceRegistry.getDictionaryService();
this.eventHelper = eventHelper;
}
/**
* {@inheritDoc}
*/
public String getId()
{
return nodeRef.toString();
}
/**
* {@inheritDoc}
*/
public ChannelType getChannelType()
{
return channelType;
}
/**
* {@inheritDoc}
*/
public String getName()
{
return name;
}
/**
* {@inheritDoc}
*/
public NodeRef getNodeRef()
{
return nodeRef;
}
/**
* {@inheritDoc}
*/
public Map getProperties()
{
return channelHelper.getChannelProperties(nodeRef);
}
public void publishEvent(PublishingEvent event)
{
NodeRef eventNode = eventHelper.getPublishingEventNode(event.getId());
for (PublishingPackageEntry entry : event.getPackage().getEntries())
{
if (entry.isPublish())
{
publishEntry(entry, eventNode);
}
else
{
unpublishEntry(entry);
}
}
}
public void unpublishEntry(final PublishingPackageEntry entry)
{
final NodeRef channelNode = getNodeRef();
if (channelHelper.hasPublishPermissions(channelNode))
{
AuthenticationUtil.runAsSystem(new RunAsWork()
{
@Override
public NodeRef doWork() throws Exception
{
NodeRef unpublishedNode = channelHelper.mapSourceToEnvironment(entry.getNodeRef(), channelNode);
if (NodeUtils.exists(unpublishedNode, nodeService))
{
unpublish(unpublishedNode);
// Need to set as temporary to delete node instead of archiving.
nodeService.addAspect(unpublishedNode, ContentModel.ASPECT_TEMPORARY, null);
nodeService.deleteNode(unpublishedNode);
}
return unpublishedNode;
}
});
}
}
public NodeRef publishEntry(final PublishingPackageEntry entry, final NodeRef eventNode)
{
NodeRef publishedNode;
//We decouple the permissions needed to publish from the permissions needed to do what's
//necessary to actually do the publish. If that makes sense...
//For example, a user may be able to publish to a channel even if they do not have permission
//to add an aspect to a published node (which is a necessary part of the publishing process).
if (channelHelper.hasPublishPermissions(getNodeRef()))
{
publishedNode = AuthenticationUtil.runAsSystem(new RunAsWork()
{
@Override
public NodeRef doWork() throws Exception
{
NodeRef publishedNode = channelHelper.mapSourceToEnvironment(entry.getNodeRef(), getNodeRef());
if (publishedNode == null)
{
publishedNode = publishNewNode(getNodeRef(), entry.getSnapshot());
}
else
{
updatePublishedNode(publishedNode, entry);
}
eventHelper.linkToLastEvent(publishedNode, eventNode);
publish(publishedNode);
return publishedNode;
}
});
}
else
{
throw new AccessDeniedException(PERMISSIONS_ERR_ACCESS_DENIED);
}
return publishedNode;
}
/**
* Creates a new node under the root of the specified channel. The type,
* aspects and properties of the node are determined by the supplied
* snapshot.
*
* @param channel
* @param snapshot
* @return the newly published node.
*/
private NodeRef publishNewNode(NodeRef channel, NodeSnapshot snapshot)
{
ParameterCheck.mandatory("channel", channel);
ParameterCheck.mandatory("snapshot", snapshot);
NodeRef publishedNode = createPublishedNode(channel, snapshot);
addAspects(publishedNode, snapshot.getAspects());
NodeRef source = snapshot.getNodeRef();
channelHelper.createMapping(source, publishedNode);
return publishedNode;
}
private void updatePublishedNode(NodeRef publishedNode, PublishingPackageEntry entry)
{
NodeSnapshot snapshot = entry.getSnapshot();
Set newAspects = snapshot.getAspects();
removeUnwantedAspects(publishedNode, newAspects);
Map snapshotProps = snapshot.getProperties();
removeUnwantedProperties(publishedNode, snapshotProps);
// Add new properties
Map newProps= new HashMap(snapshotProps);
newProps.remove(ContentModel.PROP_NODE_UUID);
nodeService.setProperties(publishedNode, snapshotProps);
// Add new aspects
addAspects(publishedNode, newAspects);
List assocs = nodeService.getChildAssocs(publishedNode, ASSOC_LAST_PUBLISHING_EVENT, RegexQNamePattern.MATCH_ALL);
for (ChildAssociationRef assoc : assocs)
{
nodeService.removeChildAssociation(assoc);
}
}
/**
* @param publishedNode
* @param snapshotProps
*/
private void removeUnwantedProperties(NodeRef publishedNode, Map snapshotProps)
{
Map publishProps = nodeService.getProperties(publishedNode);
Set propsToRemove = new HashSet(publishProps.keySet());
propsToRemove.removeAll(snapshotProps.keySet());
//We want to retain the published asset id and URL in the updated node...
snapshotProps.put(PublishingModel.PROP_ASSET_ID, nodeService.getProperty(publishedNode,
PublishingModel.PROP_ASSET_ID));
snapshotProps.put(PublishingModel.PROP_ASSET_URL, nodeService.getProperty(publishedNode,
PublishingModel.PROP_ASSET_URL));
for (QName propertyToRemove : propsToRemove)
{
nodeService.removeProperty(publishedNode, propertyToRemove);
}
}
/**
* @param publishedNode
* @param newAspects
*/
private void removeUnwantedAspects(NodeRef publishedNode, Set newAspects)
{
Set aspectsToRemove = nodeService.getAspects(publishedNode);
aspectsToRemove.removeAll(newAspects);
aspectsToRemove.remove(ASPECT_PUBLISHED);
aspectsToRemove.remove(PublishingModel.ASPECT_ASSET);
for (QName publishedAssetAspect : dictionaryService.getSubAspects(PublishingModel.ASPECT_ASSET, true))
{
aspectsToRemove.remove(publishedAssetAspect);
}
for (QName aspectToRemove : aspectsToRemove)
{
nodeService.removeAspect(publishedNode, aspectToRemove);
}
}
private void addAspects(NodeRef publishedNode, Collection aspects)
{
Set currentAspects = nodeService.getAspects(publishedNode);
for (QName aspect : aspects)
{
if (currentAspects.contains(aspect) == false)
{
nodeService.addAspect(publishedNode, aspect, null);
}
}
}
private NodeRef createPublishedNode(NodeRef root, NodeSnapshot snapshot)
{
QName type = snapshot.getType();
Map actualProps = getPropertiesToPublish(snapshot);
String name = (String) actualProps.get(ContentModel.PROP_NAME);
if (name == null)
{
name = GUID.generate();
}
QName assocName = QName.createQName(NAMESPACE, name);
ChildAssociationRef publishedAssoc = nodeService.createNode(root, PublishingModel.ASSOC_PUBLISHED_NODES, assocName, type, actualProps);
NodeRef publishedNode = publishedAssoc.getChildRef();
return publishedNode;
}
private Map getPropertiesToPublish(NodeSnapshot snapshot)
{
Map properties = snapshot.getProperties();
// Remove the Node Ref Id
Map actualProps = new HashMap(properties);
actualProps.remove(ContentModel.PROP_NODE_UUID);
return actualProps;
}
private void publish(NodeRef nodeToPublish)
{
if (channelHelper.canPublish(nodeToPublish, channelType))
{
channelHelper.addPublishedAspect(nodeToPublish, nodeRef);
channelType.publish(nodeToPublish, getProperties());
}
}
private void unpublish(NodeRef nodeToUnpublish)
{
if (channelType.canUnpublish())
{
channelType.unpublish(nodeToUnpublish, getProperties());
}
}
/**
* {@inheritDoc}
*/
public void sendStatusUpdate(String status, String nodeUrl)
{
if (channelType.canPublishStatusUpdates())
{
int urlLength = nodeUrl == null ? 0 : nodeUrl.length();
int maxLength = channelType.getMaximumStatusLength() - urlLength;
if (maxLength > 0)
{
int endpoint = Math.min(maxLength, status.length());
status = status.substring(0, endpoint );
}
String msg = nodeUrl == null ? status : status + nodeUrl;
channelType.sendStatusUpdate(this, msg);
}
}
/**
* {@inheritDoc}
*/
public String getUrl(NodeRef publishedNode)
{
NodeRef mappedNode = channelHelper.mapSourceToEnvironment(publishedNode, nodeRef);
return channelType.getNodeUrl(mappedNode);
}
/**
* {@inheritDoc}
*/
public boolean isAuthorised()
{
return channelHelper.isChannelAuthorised(nodeRef);
}
/**
* {@inheritDoc}
*/
public boolean canPublish()
{
return channelType.canPublish() &&
isAuthorised() &&
channelHelper.hasPublishPermissions(nodeRef);
}
/**
* {@inheritDoc}
*/
public boolean canUnpublish()
{
return channelType.canPublish() &&
isAuthorised() &&
channelHelper.hasPublishPermissions(nodeRef);
}
/**
* {@inheritDoc}
*/
public boolean canPublishStatusUpdates()
{
return channelType.canPublish() &&
isAuthorised() &&
channelHelper.hasPublishPermissions(nodeRef);
}
}