Implemented Unpublishing of content.

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@29593 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
N Smith
2011-08-07 15:42:23 +00:00
parent 1ebd026918
commit 9696a74002
15 changed files with 149 additions and 77 deletions

View File

@@ -290,17 +290,6 @@
<many>false</many>
</target>
</association>
<association name="pub:publishedChannel">
<source>
<mandatory>false</mandatory>
<many>true</many>
</source>
<target>
<class>pub:DeliveryChannel</class>
<mandatory>false</mandatory>
<many>false</many>
</target>
</association>
</associations>
</aspect>

View File

@@ -21,7 +21,6 @@ package org.alfresco.repo.publishing;
import static org.alfresco.model.ContentModel.ASSOC_CONTAINS;
import static org.alfresco.repo.publishing.PublishingModel.ASPECT_PUBLISHED;
import static org.alfresco.repo.publishing.PublishingModel.ASSOC_PUBLISHED_CHANNEL;
import static org.alfresco.repo.publishing.PublishingModel.ASSOC_SOURCE;
import static org.alfresco.repo.publishing.PublishingModel.PROP_CHANNEL;
import static org.alfresco.repo.publishing.PublishingModel.PROP_CHANNEL_TYPE;
@@ -132,13 +131,8 @@ public class ChannelHelper
public Boolean apply(AssociationRef assoc)
{
NodeRef publishedNode = assoc.getSourceRef();
List<AssociationRef> channelAssoc = nodeService.getTargetAssocs(publishedNode, ASSOC_PUBLISHED_CHANNEL);
if(CollectionUtils.isEmpty(channelAssoc))
{
return false;
}
NodeRef target = channelAssoc.get(0).getTargetRef();
return target.equals(channelNode);
NodeRef parent = nodeService.getPrimaryParent(publishedNode).getParentRef();
return channelNode.equals(parent);
}
};
AssociationRef assoc = CollectionUtils.findFirst(sourceAssocs, acceptor);
@@ -322,11 +316,6 @@ public class ChannelHelper
public void addPublishedAspect(NodeRef publishedNode, NodeRef channelNode)
{
nodeService.addAspect(publishedNode, ASPECT_PUBLISHED, null);
List<AssociationRef> channelAssoc = nodeService.getTargetAssocs(publishedNode, ASSOC_PUBLISHED_CHANNEL);
if(CollectionUtils.isEmpty(channelAssoc))
{
nodeService.createAssociation(publishedNode, channelNode, ASSOC_PUBLISHED_CHANNEL);
}
}
private List<ChildAssociationRef> getChannelAssocs(NodeRef channelContainer)

View File

@@ -50,7 +50,6 @@ public class ChannelImpl implements Channel
/**
* {@inheritDoc}
*/
@Override
public String getId()
{
return nodeRef.toString();
@@ -91,7 +90,6 @@ public class ChannelImpl implements Channel
/**
* {@inheritDoc}
*/
@Override
public void publish(NodeRef nodeToPublish)
{
channelHelper.addPublishedAspect(nodeToPublish, nodeRef);
@@ -104,17 +102,17 @@ public class ChannelImpl implements Channel
/**
* {@inheritDoc}
*/
@Override
public void unPublish(NodeRef nodeToUnpublish)
{
// TODO Auto-generated method stub
if(channelType.canUnpublish())
{
channelType.unpublish(nodeToUnpublish, getProperties());
}
}
/**
* {@inheritDoc}
*/
@Override
public void updateStatus(String status)
{
channelType.updateStatus(this, status, getProperties());

View File

@@ -30,6 +30,7 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.transfer.manifest.TransferManifestNodeFactory;
import org.alfresco.repo.transfer.manifest.TransferManifestNormalNode;
import org.alfresco.service.cmr.publishing.PublishingPackage;
import org.alfresco.service.cmr.publishing.MutablePublishingPackage;
import org.alfresco.service.cmr.publishing.PublishingPackageEntry;
import org.alfresco.service.cmr.repository.NodeRef;
@@ -40,6 +41,7 @@ import org.alfresco.service.cmr.version.VersionService;
* @author Brian
* @author Nick Smith
*
* @since 4.0
*/
public class MutablePublishingPackageImpl implements MutablePublishingPackage
{
@@ -62,15 +64,15 @@ public class MutablePublishingPackageImpl implements MutablePublishingPackage
/**
* {@inheritDoc}
*/
public void addNodesToPublish(NodeRef... nodesToAdd)
public MutablePublishingPackage addNodesToPublish(NodeRef... nodesToAdd)
{
addNodesToPublish(Arrays.asList(nodesToAdd));
return addNodesToPublish(Arrays.asList(nodesToAdd));
}
/**
* {@inheritDoc}
*/
public void addNodesToPublish(final Collection<NodeRef> nodesToAdd)
public MutablePublishingPackage addNodesToPublish(final Collection<NodeRef> nodesToAdd)
{
AuthenticationUtil.runAs(new RunAsWork<Void>()
{
@@ -81,6 +83,7 @@ public class MutablePublishingPackageImpl implements MutablePublishingPackage
}
}, AuthenticationUtil.getSystemUserName());
nodesToPublish.addAll(nodesToAdd);
return this;
}
private void versionNodes(Collection<NodeRef> nodesToAdd)
@@ -105,27 +108,27 @@ public class MutablePublishingPackageImpl implements MutablePublishingPackage
/**
* {@inheritDoc}
*/
public void addNodesToUnpublish(NodeRef... nodesToRemove)
public MutablePublishingPackage addNodesToUnpublish(NodeRef... nodesToRemove)
{
addNodesToUnpublish(Arrays.asList(nodesToRemove));
return addNodesToUnpublish(Arrays.asList(nodesToRemove));
}
/**
* {@inheritDoc}
*/
public void addNodesToUnpublish(Collection<NodeRef> nodesToRemove)
public MutablePublishingPackage addNodesToUnpublish(Collection<NodeRef> nodesToRemove)
{
for (NodeRef nodeRef : nodesToRemove)
{
entryMap.put(nodeRef, new PublishingPackageEntryImpl(false, nodeRef, null, null));
}
nodesToUnpublish.addAll(nodesToRemove);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public Collection<PublishingPackageEntry> getEntries()
{
return entryMap.values();
@@ -134,7 +137,6 @@ public class MutablePublishingPackageImpl implements MutablePublishingPackage
/**
* {@inheritDoc}
*/
@Override
public Set<NodeRef> getNodesToPublish()
{
return nodesToPublish;
@@ -143,7 +145,6 @@ public class MutablePublishingPackageImpl implements MutablePublishingPackage
/**
* {@inheritDoc}
*/
@Override
public Set<NodeRef> getNodesToUnpublish()
{
return nodesToUnpublish;
@@ -152,9 +153,16 @@ public class MutablePublishingPackageImpl implements MutablePublishingPackage
/**
* {@inheritDoc}
*/
@Override
public Map<NodeRef, PublishingPackageEntry> getEntryMap()
{
return entryMap;
}
/**
* {@inheritDoc}
*/
public PublishingPackage build()
{
return new PublishingPackageImpl(entryMap);
}
}

View File

@@ -204,6 +204,26 @@ public class PublishEventActionTest extends AbstractPublishingIntegrationTest
assertFalse(publishedProps.containsKey(PROP_LONGITUDE));
}
public void testUnpublishNode() throws Exception
{
// Create content node and publish.
NodeRef source = testHelper.createContentNode(contentNodeName, content, MimetypeMap.MIMETYPE_TEXT_PLAIN);
publishNode(source);
// Check published node exists
NodeRef publishedNode = channelHelper.mapSourceToEnvironment(source, channelNode);
assertNotNull(publishedNode);
assertTrue(nodeService.exists(publishedNode));
// Unpublish source node.
publishNode(source, null, false);
// Check the published node no longer exists.
assertFalse(nodeService.exists(publishedNode));
publishedNode = channelHelper.mapSourceToEnvironment(source, channelNode);
assertNull(publishedNode);
}
@SuppressWarnings("unchecked")
@Test
public void testChannelTypePublishIsCalledOnPublish() throws Exception
@@ -242,6 +262,28 @@ public class PublishEventActionTest extends AbstractPublishingIntegrationTest
// Check publish was called on update
verify(channelType, times(1)).publish(eq(publishedNode), anyMap());
// Unpublish node.
publishNode(source, null, false);
// Check unpublish was not called, since ChannelType doesn't support it.
verify(channelType, never()).unpublish(eq(publishedNode), anyMap());
// Enable unpublishing on ChannelType.
when(channelType.canUnpublish()).thenReturn(true);
// Re-publish the node.
publishNode(source);
// Get the newly published node.
publishedNode = channelHelper.mapSourceToEnvironment(source, channelNode);
assertNotNull(publishedNode);
// Unpublish the node
publishNode(source, null, false);
// Check unpublish was called on update
verify(channelType, times(1)).unpublish(eq(publishedNode), anyMap());
}
@Test
@@ -339,8 +381,20 @@ public class PublishEventActionTest extends AbstractPublishingIntegrationTest
private NodeRef publishNode(NodeRef source, StatusUpdate statusUpdate)
{
MutablePublishingPackage pckg = publishingService.getPublishingQueue().createPublishingPackage();
return publishNode(source, statusUpdate, true);
}
private NodeRef publishNode(NodeRef source, StatusUpdate statusUpdate, boolean publish)
{
MutablePublishingPackage pckg = publishingService.getPublishingQueue().createPublishingPackageBuilder();
if(publish)
{
pckg.addNodesToPublish(source);
}
else
{
pckg.addNodesToUnpublish(source);
}
String eventId = testHelper.scheduleEvent1Year(pckg, channel.getId(), null, statusUpdate);
assertNotNull(eventId);

View File

@@ -42,6 +42,7 @@ import static org.alfresco.util.collections.CollectionUtils.isEmpty;
import static org.alfresco.util.collections.CollectionUtils.toListOfStrings;
import static org.alfresco.util.collections.CollectionUtils.transform;
import static org.alfresco.util.collections.CollectionUtils.transformFlat;
import static org.alfresco.util.collections.CollectionUtils.transformToMap;
import java.io.InputStream;
import java.io.OutputStream;
@@ -64,6 +65,7 @@ import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.service.cmr.publishing.PublishingEvent;
import org.alfresco.service.cmr.publishing.PublishingEventFilter;
import org.alfresco.service.cmr.publishing.PublishingPackage;
import org.alfresco.service.cmr.publishing.PublishingPackageEntry;
import org.alfresco.service.cmr.publishing.Status;
import org.alfresco.service.cmr.publishing.StatusUpdate;
import org.alfresco.service.cmr.repository.AssociationRef;
@@ -82,6 +84,7 @@ import org.alfresco.service.cmr.workflow.WorkflowTask;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.util.GUID;
import org.alfresco.util.collections.CollectionUtils;
import org.alfresco.util.collections.Filter;
import org.alfresco.util.collections.Function;
import org.apache.commons.logging.Log;
@@ -498,7 +501,10 @@ public class PublishingEventHelper
InputStream input = contentReader.getContentInputStream();
try
{
return serializer.deserialize(input);
Map<NodeRef, PublishingPackageEntry> publishEntires = serializer.deserialize(input);
Map<NodeRef, PublishingPackageEntry> allEntries = getUnpublishPackageEntries(eventNode);
allEntries.putAll(publishEntires);
return new PublishingPackageImpl(allEntries);
}
catch (Exception ex)
{
@@ -507,6 +513,28 @@ public class PublishingEventHelper
}
}
private Map<NodeRef, PublishingPackageEntry> getUnpublishPackageEntries(NodeRef eventNode)
{
@SuppressWarnings("unchecked")
List<String> entries= (List<String>) nodeService.getProperty(eventNode, PROP_PUBLISHING_EVENT_NODES_TO_UNPUBLISH);
if(CollectionUtils.isEmpty(entries))
{
return new HashMap<NodeRef, PublishingPackageEntry>();
}
List<NodeRef> nodes = NodeUtils.toNodeRefs(entries);
return transformToMap(nodes, new Function<NodeRef, PublishingPackageEntry>()
{
public PublishingPackageEntry apply(NodeRef node)
{
if(NodeUtils.exists(node, nodeService))
{
return new PublishingPackageEntryImpl(false, node, null, null);
}
return null;
}
});
}
public void cancelEvent(String id)
{
NodeRef eventNode = getPublishingEventNode(id);

View File

@@ -85,7 +85,7 @@ public class PublishingIntegratedTest extends BaseSpringTest
}
PublishingQueue liveQueue = publishingService.getPublishingQueue();
MutablePublishingPackage publishingPackage = liveQueue.createPublishingPackage();
MutablePublishingPackage publishingPackage = liveQueue.createPublishingPackageBuilder();
publishingPackage.addNodesToPublish(nodes);
Calendar scheduleTime = Calendar.getInstance();
@@ -118,7 +118,7 @@ public class PublishingIntegratedTest extends BaseSpringTest
NamespaceService.CONTENT_MODEL_1_0_URI, Integer.toString(i)), ContentModel.TYPE_CONTENT).getChildRef());
}
PublishingQueue liveQueue = publishingService.getPublishingQueue();
MutablePublishingPackage publishingPackage = liveQueue.createPublishingPackage();
MutablePublishingPackage publishingPackage = liveQueue.createPublishingPackageBuilder();
publishingPackage.addNodesToPublish(nodes);
Calendar scheduleTime = Calendar.getInstance();

View File

@@ -91,7 +91,6 @@ public interface PublishingModel
public static final QName ASSOC_PUBLISHING_EVENT = QName.createQName(NAMESPACE, "publishingEventAssoc");
public static final QName ASSOC_SOURCE = QName.createQName(NAMESPACE, "source");
public static final QName ASSOC_LAST_PUBLISHING_EVENT= QName.createQName(NAMESPACE, "lastPublishingEvent");
public static final QName ASSOC_PUBLISHED_CHANNEL= QName.createQName(NAMESPACE, "publishedChannel");
// Workflow Properties
public static final QName PROP_WF_PUBLISHING_EVENT= QName.createQName(WF_NAMESPACE, "publishingEvent");

View File

@@ -21,16 +21,21 @@ package org.alfresco.repo.publishing;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
import org.alfresco.service.cmr.publishing.PublishingPackage;
import org.alfresco.service.cmr.publishing.PublishingPackageEntry;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* @author Brian
* @author Nick Smith
*
* @since 4.0
*/
public interface PublishingPackageSerializer
{
void serialize(PublishingPackage publishingPackage, OutputStream output) throws Exception;
PublishingPackage deserialize(InputStream input) throws Exception;
Map<NodeRef, PublishingPackageEntry> deserialize(InputStream input) throws Exception;
}

View File

@@ -19,8 +19,6 @@
package org.alfresco.repo.publishing;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -144,8 +142,7 @@ public class PublishingPackageSerializerTest extends AbstractPublishingIntegrati
byte[] output = os.toByteArray();
ByteArrayInputStream is = new ByteArrayInputStream(output);
PublishingPackageImpl deserializedPublishingPackage = (PublishingPackageImpl) serializer.deserialize(is);
Map<NodeRef,PublishingPackageEntry> entryMap = deserializedPublishingPackage.getEntryMap();
Map<NodeRef, PublishingPackageEntry> entryMap = serializer.deserialize(is);
assertEquals(1, entryMap.size());
assertTrue(entryMap.containsKey(normalNode1.getNodeRef()));
PublishingPackageEntryImpl entry = (PublishingPackageEntryImpl) entryMap.get(normalNode1.getNodeRef());

View File

@@ -60,7 +60,7 @@ public class PublishingQueueImpl implements PublishingQueue
/**
* {@inheritDoc}
*/
public MutablePublishingPackage createPublishingPackage()
public MutablePublishingPackage createPublishingPackageBuilder()
{
return new MutablePublishingPackageImpl(transferManifestNodeFactory, versionService);
}

View File

@@ -42,7 +42,6 @@ import org.alfresco.service.cmr.publishing.PublishingService;
import org.alfresco.service.cmr.publishing.Status;
import org.alfresco.service.cmr.publishing.StatusUpdate;
import org.alfresco.service.cmr.publishing.channels.Channel;
import org.alfresco.service.cmr.publishing.channels.ChannelType;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.workflow.WorkflowInstance;
@@ -73,12 +72,11 @@ public class PublishingQueueImplTest extends AbstractPublishingIntegrationTest
assertNull(nodeService.getProperty(firstNode, PROP_VERSION_LABEL));
assertNull(nodeService.getProperty(firstNode, PROP_VERSION_LABEL));
MutablePublishingPackage publishingPackage = publishingService.getPublishingQueue().createPublishingPackage();
MutablePublishingPackage publishingPackage = publishingService.getPublishingQueue().createPublishingPackageBuilder();
publishingPackage.addNodesToPublish(firstNode, secondNode);
//TODO Implement Unpublish
// NodeRef thirdNode = fileFolderService.create(docLib, "third", ContentModel.TYPE_CONTENT).getNodeRef();
// publishingPackage.addNodesToUnpublish(thirdNode);
NodeRef thirdNode = createContent("third");
publishingPackage.addNodesToUnpublish(thirdNode);
Calendar schedule = Calendar.getInstance();
schedule.add(Calendar.HOUR, 2);
@@ -103,9 +101,9 @@ public class PublishingQueueImplTest extends AbstractPublishingIntegrationTest
ArrayList<NodeRef> toUnpublish = new ArrayList<NodeRef>(1);
for (PublishingPackageEntry entry : pckg.getEntries())
{
assertNotNull(entry.getSnapshot());
if(entry.isPublish())
{
assertNotNull(entry.getSnapshot());
toPublish.add(entry.getNodeRef());
}
else
@@ -118,8 +116,8 @@ public class PublishingQueueImplTest extends AbstractPublishingIntegrationTest
assertTrue(toPublish.contains(firstNode));
assertTrue(toPublish.contains(secondNode));
// assertEquals(1, toUnpublish.size());
// assertTrue(toUnpublish.contains(thirdNode));
assertEquals(1, toUnpublish.size());
assertTrue(toUnpublish.contains(thirdNode));
// Check the correct version is recorded in the entry.
PublishingPackageEntry entry = publishingPackage.getEntryMap().get(firstNode);
@@ -148,7 +146,7 @@ public class PublishingQueueImplTest extends AbstractPublishingIntegrationTest
StatusUpdate update = publishingService.getPublishingQueue().createStatusUpdate(message, secondNode, channelNames);
// Publish an event with the StatusUpdate
MutablePublishingPackage publishingPackage = publishingService.getPublishingQueue().createPublishingPackage();
MutablePublishingPackage publishingPackage = publishingService.getPublishingQueue().createPublishingPackageBuilder();
publishingPackage.addNodesToPublish(firstNode, secondNode);
Calendar schedule = Calendar.getInstance();
schedule.add(Calendar.HOUR, 2);
@@ -167,7 +165,7 @@ public class PublishingQueueImplTest extends AbstractPublishingIntegrationTest
public void testScheduleNewEventPermissions() throws Exception
{
// Create Channels as Admin
ChannelType channelType = testHelper.mockChannelType(channelTypeId);
testHelper.mockChannelType(channelTypeId);
Channel publishChannel = testHelper.createChannel(channelTypeId);
Channel statusChannel = testHelper.createChannel(channelTypeId);
@@ -182,7 +180,7 @@ public class PublishingQueueImplTest extends AbstractPublishingIntegrationTest
personManager.setUser(user1);
// Publish an event
MutablePublishingPackage publishingPackage = publishingService.getPublishingQueue().createPublishingPackage();
MutablePublishingPackage publishingPackage = publishingService.getPublishingQueue().createPublishingPackageBuilder();
publishingPackage.addNodesToPublish(firstNode, secondNode);
try
{

View File

@@ -25,10 +25,10 @@ import java.io.OutputStreamWriter;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
@@ -54,8 +54,9 @@ public class StandardPublishingPackageSerializer implements PublishingPackageSer
{
/**
* {@inheritDoc}
* @return
*/
public PublishingPackage deserialize(InputStream input) throws Exception
public Map<NodeRef, PublishingPackageEntry> deserialize(InputStream input) throws Exception
{
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
SAXParser parser = saxParserFactory.newSAXParser();
@@ -63,8 +64,7 @@ public class StandardPublishingPackageSerializer implements PublishingPackageSer
XMLTransferManifestReader xmlReader = new XMLTransferManifestReader(processor);
parser.parse(input, xmlReader);
PublishingPackageImpl publishingPackage = new PublishingPackageImpl(processor.getEntries());
return publishingPackage;
return processor.getEntries();
}
/**
@@ -74,11 +74,10 @@ public class StandardPublishingPackageSerializer implements PublishingPackageSer
{
try
{
Collection<PublishingPackageEntry> entries = publishingPackage.getEntries();
Set<NodeRef> nodesToPublish = publishingPackage.getNodesToPublish();
TransferManifestHeader header = new TransferManifestHeader();
header.setCreatedDate(new Date());
header.setNodeCount(entries.size());
header.setNodeCount(nodesToPublish.size());
header.setReadOnly(false);
header.setSync(false);
@@ -87,8 +86,12 @@ public class StandardPublishingPackageSerializer implements PublishingPackageSer
XMLTransferManifestWriter transferManifestWriter = new XMLTransferManifestWriter();
transferManifestWriter.startTransferManifest(writer);
transferManifestWriter.writeTransferManifestHeader(header);
for (PublishingPackageEntry entry : entries)
// Iterate over NodesToPublish and Serialize.
Map<NodeRef, PublishingPackageEntry> entryMap = publishingPackage.getEntryMap();
for (NodeRef publishNode: nodesToPublish)
{
PublishingPackageEntry entry = entryMap.get(publishNode);
if (entry instanceof PublishingPackageEntryImpl)
{
PublishingPackageEntryImpl entryImpl = (PublishingPackageEntryImpl)entry;

View File

@@ -23,17 +23,21 @@ import java.util.Collection;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* An extension of the {@link PublishingPackage} interface that permits changes to be made
* An extendsion of the {@link PublishingPackage} interface which allows values to be modified.
* @author Brian
* @author Nick Smith
*
* @since 4.0
*/
public interface MutablePublishingPackage extends PublishingPackage
{
void addNodesToUnpublish(NodeRef... nodesToRemove);
MutablePublishingPackage addNodesToUnpublish(NodeRef... nodesToRemove);
void addNodesToUnpublish(Collection<NodeRef> nodesToRemove);
MutablePublishingPackage addNodesToUnpublish(Collection<NodeRef> nodesToRemove);
void addNodesToPublish(NodeRef... nodesToPublish);
MutablePublishingPackage addNodesToPublish(NodeRef... nodesToPublish);
void addNodesToPublish(Collection<NodeRef> nodesToPublish);
MutablePublishingPackage addNodesToPublish(Collection<NodeRef> nodesToPublish);
PublishingPackage build();
}

View File

@@ -31,7 +31,7 @@ public interface PublishingQueue
* a call to the {@link PublishingQueue#scheduleNewEvent(PublishingPackage, String, Calendar, String, StatusUpdate)} operation.
* @return A publishing package that can be populated before being placed on the publishing queue.
*/
MutablePublishingPackage createPublishingPackage();
MutablePublishingPackage createPublishingPackageBuilder();
StatusUpdate createStatusUpdate(String message, NodeRef nodeToLinkTo, String... channelIds);
StatusUpdate createStatusUpdate(String message, NodeRef nodeToLinkTo, Collection<String> channelIds);