diff --git a/config/alfresco/action-services-context.xml b/config/alfresco/action-services-context.xml index 56d8746fd6..21d0f5eede 100644 --- a/config/alfresco/action-services-context.xml +++ b/config/alfresco/action-services-context.xml @@ -13,10 +13,10 @@ defaultAsyncAction - 2 + 8 - 10 + 20 diff --git a/config/alfresco/content-publishing-context.xml b/config/alfresco/content-publishing-context.xml index 42899cad52..0943c2ac94 100644 --- a/config/alfresco/content-publishing-context.xml +++ b/config/alfresco/content-publishing-context.xml @@ -56,6 +56,7 @@ + @@ -63,6 +64,8 @@ + + @@ -95,13 +98,12 @@ - - + diff --git a/config/alfresco/flickr-publishing-context.xml b/config/alfresco/flickr-publishing-context.xml index a99c9e1d00..0b662f4895 100644 --- a/config/alfresco/flickr-publishing-context.xml +++ b/config/alfresco/flickr-publishing-context.xml @@ -11,9 +11,10 @@ - - + + + @@ -26,18 +27,4 @@ - - - - - - - - - - - - - - diff --git a/config/alfresco/slideshare-publishing-context.xml b/config/alfresco/slideshare-publishing-context.xml index d4f79d04d0..b3f5aeeef1 100644 --- a/config/alfresco/slideshare-publishing-context.xml +++ b/config/alfresco/slideshare-publishing-context.xml @@ -11,34 +11,19 @@ - - + - - + + - - - - - - - - - - - - - - diff --git a/config/alfresco/youtube-publishing-context.xml b/config/alfresco/youtube-publishing-context.xml index 8f049155fe..fbfbf5093b 100644 --- a/config/alfresco/youtube-publishing-context.xml +++ b/config/alfresco/youtube-publishing-context.xml @@ -11,28 +11,12 @@ - - - + - - - - - - - - - - - - - - diff --git a/config/test/alfresco/test-web-publishing-context.xml b/config/test/alfresco/test-web-publishing-context.xml index cf1ba175aa..22cc9de35c 100644 --- a/config/test/alfresco/test-web-publishing-context.xml +++ b/config/test/alfresco/test-web-publishing-context.xml @@ -100,6 +100,10 @@ + + + + diff --git a/source/java/org/alfresco/repo/publishing/AbstractChannelType.java b/source/java/org/alfresco/repo/publishing/AbstractChannelType.java index 175f3c8180..f3cdef5978 100644 --- a/source/java/org/alfresco/repo/publishing/AbstractChannelType.java +++ b/source/java/org/alfresco/repo/publishing/AbstractChannelType.java @@ -20,13 +20,17 @@ package org.alfresco.repo.publishing; import java.io.Serializable; +import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Set; import org.alfresco.repo.node.encryption.MetadataEncryptor; import org.alfresco.service.cmr.publishing.channels.Channel; import org.alfresco.service.cmr.publishing.channels.ChannelService; import org.alfresco.service.cmr.publishing.channels.ChannelType; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.QName; import org.alfresco.util.ParameterCheck; import org.springframework.core.io.ClassPathResource; @@ -36,8 +40,9 @@ import org.springframework.core.io.Resource; * @author Nick Smith * @since 4.0 */ -public abstract class AbstractChannelType implements ChannelType +public abstract class AbstractChannelType implements ChannelType, ChannelTypePublishingOperations { + private NodeService nodeService; private ChannelService channelService; private MetadataEncryptor encryptor; @@ -62,6 +67,16 @@ public abstract class AbstractChannelType implements ChannelType return encryptor; } + protected NodeService getNodeService() + { + return nodeService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + /** * {@inheritDoc} */ @@ -132,4 +147,46 @@ public abstract class AbstractChannelType implements ChannelType { return "png"; } + + @Override + public Set getSupportedContentTypes() + { + return Collections.emptySet(); + } + + @Override + public Set getSupportedMimeTypes() + { + return Collections.emptySet(); + } + + @Override + public void sendStatusUpdate(Channel channel, String status) + { + throw new UnsupportedOperationException(); + } + + @Override + public void publish(NodeRef nodeToPublish, Map channelProperties) + { + throw new UnsupportedOperationException(); + } + + @Override + public void unpublish(NodeRef nodeToUnpublish, Map channelProperties) + { + throw new UnsupportedOperationException(); + } + + @Override + public String getNodeUrl(NodeRef node) + { + String url = null; + if (node != null && nodeService.exists(node) && nodeService.hasAspect(node, PublishingModel.ASPECT_ASSET)) + { + url = (String)nodeService.getProperty(node, PublishingModel.PROP_ASSET_URL); + } + return url; + } + } diff --git a/source/java/org/alfresco/repo/publishing/AbstractOAuth1ChannelType.java b/source/java/org/alfresco/repo/publishing/AbstractOAuth1ChannelType.java index 6cfbcaaa4e..9885d86565 100644 --- a/source/java/org/alfresco/repo/publishing/AbstractOAuth1ChannelType.java +++ b/source/java/org/alfresco/repo/publishing/AbstractOAuth1ChannelType.java @@ -43,17 +43,11 @@ import org.springframework.social.oauth1.OAuthToken; */ public abstract class AbstractOAuth1ChannelType extends AbstractChannelType { - private NodeService nodeService; private OAuth1ConnectionFactory connectionFactory; - public Connection getConnectionForPublishNode(NodeRef publishNode) - { - NodeRef channelNode = nodeService.getPrimaryParent(publishNode).getParentRef(); - return getConnectionForChannel(channelNode); - } - - public Connection getConnectionForChannel(NodeRef channelNode) + protected Connection getConnectionForChannel(NodeRef channelNode) { + NodeService nodeService = getNodeService(); Connection connection = null; if (nodeService.exists(channelNode) && nodeService.hasAspect(channelNode, PublishingModel.ASPECT_OAUTH1_DELIVERY_CHANNEL)) @@ -73,11 +67,6 @@ public abstract class AbstractOAuth1ChannelType extends AbstractChannelType return connection; } - protected NodeService getNodeService() - { - return nodeService; - } - @Override public String getAuthorisationUrl(Channel channel, String callbackUrl) { @@ -88,6 +77,7 @@ public abstract class AbstractOAuth1ChannelType extends AbstractChannelType throw new IllegalArgumentException("Invalid channel type: " + channel.getChannelType().getId()); } + NodeService nodeService = getNodeService(); OAuth1Operations oauthOperations = getOAuth1Operations(); OAuthToken requestToken = oauthOperations.fetchRequestToken(callbackUrl, null); @@ -104,6 +94,7 @@ public abstract class AbstractOAuth1ChannelType extends AbstractChannelType protected AuthStatus internalAcceptAuthorisation(Channel channel, Map callbackHeaders, Map callbackParams) { + NodeService nodeService = getNodeService(); AuthStatus authorised = AuthStatus.UNAUTHORISED; String[] verifier = callbackParams.get(getOAuthVerifierParamName()); if (verifier != null) @@ -158,12 +149,4 @@ public abstract class AbstractOAuth1ChannelType extends AbstractChannelType { this.connectionFactory = connectionFactory; } - - /** - * @param nodeService the nodeService to set - */ - public final void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/publishing/ChannelHelper.java b/source/java/org/alfresco/repo/publishing/ChannelHelper.java index b25e703676..87fb692111 100644 --- a/source/java/org/alfresco/repo/publishing/ChannelHelper.java +++ b/source/java/org/alfresco/repo/publishing/ChannelHelper.java @@ -36,6 +36,7 @@ import java.util.Set; import org.alfresco.model.ContentModel; import org.alfresco.repo.node.NodeUtils; +import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.dictionary.TypeDefinition; @@ -70,16 +71,19 @@ public class ChannelHelper private DictionaryService dictionaryService; private FileFolderService fileFolderService; private PermissionService permissionService; + + private ServiceRegistry serviceRegistry; + private PublishingEventHelper eventHelper; public ChannelHelper() { super(); } - public ChannelHelper(NodeService nodeService, DictionaryService dictionaryService) + public ChannelHelper(ServiceRegistry serviceRegistry, PublishingEventHelper eventHelper) { - this.nodeService =nodeService; - this.dictionaryService = dictionaryService; + this.serviceRegistry = serviceRegistry; + this.eventHelper = eventHelper; } public NodeRef createChannelNode(NodeRef parent, ChannelType channelType, String channelName, @@ -105,7 +109,7 @@ public class ChannelHelper String channelTypeId = (String) props.get(PROP_CHANNEL_TYPE_ID); ChannelType channelType = channelService.getChannelType(channelTypeId); String name = (String) props.get(ContentModel.PROP_NAME); - return new ChannelImpl(channelType, nodeRef, name, this); + return new ChannelImpl(serviceRegistry, (AbstractChannelType) channelType, nodeRef, name, this, eventHelper); } /** @@ -429,4 +433,14 @@ public class ChannelHelper { this.permissionService = permissionService; } + + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + public void setEventHelper(PublishingEventHelper eventHelper) + { + this.eventHelper = eventHelper; + } } diff --git a/source/java/org/alfresco/repo/publishing/ChannelImpl.java b/source/java/org/alfresco/repo/publishing/ChannelImpl.java index 27ce056abe..6ab25a7274 100644 --- a/source/java/org/alfresco/repo/publishing/ChannelImpl.java +++ b/source/java/org/alfresco/repo/publishing/ChannelImpl.java @@ -19,13 +19,35 @@ package org.alfresco.repo.publishing; -import java.io.Serializable; -import java.util.Map; +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_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.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 @@ -35,16 +57,24 @@ import org.alfresco.service.namespace.QName; public class ChannelImpl implements Channel { private final NodeRef nodeRef; - private final ChannelType channelType; + 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(ChannelType channelType, NodeRef nodeRef, String name, ChannelHelper channelHelper) + 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; } /** @@ -87,22 +117,186 @@ public class ChannelImpl implements Channel return channelHelper.getChannelProperties(nodeRef); } - /** - * {@inheritDoc} - */ - public void publish(NodeRef nodeToPublish) + public void publishEvent(PublishingEvent event) { - channelHelper.addPublishedAspect(nodeToPublish, nodeRef); - if (channelHelper.canPublish(nodeToPublish, channelType)) + NodeRef eventNode = eventHelper.getPublishingEventNode(event.getId()); + for (PublishingPackageEntry entry : event.getPackage().getEntries()) + { + if (entry.isPublish()) + { + publishEntry(entry, eventNode); + } + else + { + unpublishEntry(entry); + } + } + } + + public void unpublishEntry(PublishingPackageEntry entry) + { + NodeRef channelNode = new NodeRef(getId()); + NodeRef publishedNode = channelHelper.mapSourceToEnvironment(entry.getNodeRef(), channelNode); + if (NodeUtils.exists(publishedNode, nodeService)) { - channelType.publish(nodeToPublish, getProperties()); + unpublish(publishedNode); + // Need to set as temporary to delete node instead of archiving. + nodeService.addAspect(publishedNode, ContentModel.ASPECT_TEMPORARY, null); + nodeService.deleteNode(publishedNode); + } + } + + public NodeRef publishEntry(PublishingPackageEntry entry, NodeRef eventNode) + { + 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; + } + + /** + * 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); } } /** - * {@inheritDoc} - */ - public void unPublish(NodeRef nodeToUnpublish) + * @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, ASSOC_CONTAINS, 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()) { @@ -113,7 +307,7 @@ public class ChannelImpl implements Channel /** * {@inheritDoc} */ - public void updateStatus(String status, String nodeUrl) + public void sendStatusUpdate(String status, String nodeUrl) { if (channelType.canPublishStatusUpdates()) { @@ -125,7 +319,7 @@ public class ChannelImpl implements Channel status = status.substring(0, endpoint ); } String msg = nodeUrl == null ? status : status + nodeUrl; - channelType.updateStatus(this, msg, getProperties()); + channelType.sendStatusUpdate(this, msg); } } diff --git a/source/java/org/alfresco/repo/publishing/ChannelImplTest.java b/source/java/org/alfresco/repo/publishing/ChannelImplTest.java index e3a092abf3..4a85669617 100644 --- a/source/java/org/alfresco/repo/publishing/ChannelImplTest.java +++ b/source/java/org/alfresco/repo/publishing/ChannelImplTest.java @@ -25,7 +25,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import junit.framework.TestCase; -import org.alfresco.service.cmr.publishing.channels.ChannelType; import org.alfresco.service.cmr.repository.NodeRef; /** @@ -37,75 +36,75 @@ public class ChannelImplTest extends TestCase public void testUpdateStatus() throws Exception { int maxLength = 30; - ChannelType channelType = mockChannelType(maxLength); + AbstractChannelType channelType = mockChannelType(maxLength); ChannelHelper helper = mock(ChannelHelper.class); when(helper.getChannelProperties(any(NodeRef.class))).thenReturn(null); NodeRef node = new NodeRef("test://channel/node"); - ChannelImpl channel = new ChannelImpl(channelType, node, "Name", helper); + ChannelImpl channel = new ChannelImpl(null, channelType, node, "Name", helper, null); String msg = "Here is a message"; - channel.updateStatus(msg, null); - verify(channelType).updateStatus(channel, msg, null); + channel.sendStatusUpdate(msg, null); + verify(channelType).sendStatusUpdate(channel, msg); } public void testUpdateStatusTruncates() throws Exception { int maxLength = 30; - ChannelType channelType = mockChannelType(maxLength); + AbstractChannelType channelType = mockChannelType(maxLength); ChannelHelper helper = mock(ChannelHelper.class); when(helper.getChannelProperties(any(NodeRef.class))).thenReturn(null); NodeRef node = new NodeRef("test://channel/node"); - ChannelImpl channel = new ChannelImpl(channelType, node, "Name", helper); + ChannelImpl channel = new ChannelImpl(null, channelType, node, "Name", helper, null); String msg = "Here is a much longer message to truncate."; String expMsg = msg.substring(0, maxLength); - channel.updateStatus(msg, null); - verify(channelType).updateStatus(channel, expMsg, null); + channel.sendStatusUpdate(msg, null); + verify(channelType).sendStatusUpdate(channel, expMsg); } public void testUpdateStatusTruncatesWithUrl() throws Exception { int maxLength = 30; - ChannelType channelType = mockChannelType(maxLength); + AbstractChannelType channelType = mockChannelType(maxLength); ChannelHelper helper = mock(ChannelHelper.class); when(helper.getChannelProperties(any(NodeRef.class))).thenReturn(null); NodeRef node = new NodeRef("test://channel/node"); - ChannelImpl channel = new ChannelImpl(channelType, node, "Name", helper); + ChannelImpl channel = new ChannelImpl(null, channelType, node, "Name", helper, null); String nodeUrl ="http://foo/bar"; int endpoint = maxLength - nodeUrl.length(); String msg = "Here is a much longer message to truncate."; String expMsg = msg.substring(0, endpoint) + nodeUrl; - channel.updateStatus(msg, nodeUrl); - verify(channelType).updateStatus(channel, expMsg, null); + channel.sendStatusUpdate(msg, nodeUrl); + verify(channelType).sendStatusUpdate(channel, expMsg); } public void testUpdateStatusNoMaxLength() throws Exception { - ChannelType channelType = mockChannelType(0); + AbstractChannelType channelType = mockChannelType(0); ChannelHelper helper = mock(ChannelHelper.class); when(helper.getChannelProperties(any(NodeRef.class))).thenReturn(null); NodeRef node = new NodeRef("test://channel/node"); - ChannelImpl channel = new ChannelImpl(channelType, node, "Name", helper); + ChannelImpl channel = new ChannelImpl(null, channelType, node, "Name", helper, null); String nodeUrl ="http://foo/bar"; String msg = "Here is a much longer message to truncate."; String expMsg = msg + nodeUrl; - channel.updateStatus(msg, nodeUrl); - verify(channelType).updateStatus(channel, expMsg, null); + channel.sendStatusUpdate(msg, nodeUrl); + verify(channelType).sendStatusUpdate(channel, expMsg); } - private ChannelType mockChannelType(int maxLength) + private AbstractChannelType mockChannelType(int maxLength) { - ChannelType channelType = mock(ChannelType.class); + AbstractChannelType channelType = mock(AbstractChannelType.class); when(channelType.canPublishStatusUpdates()).thenReturn(true); when(channelType.getMaximumStatusLength()).thenReturn(maxLength); return channelType; diff --git a/source/java/org/alfresco/repo/publishing/ChannelServiceImpl.java b/source/java/org/alfresco/repo/publishing/ChannelServiceImpl.java index a52baebcc8..2268f51ed7 100644 --- a/source/java/org/alfresco/repo/publishing/ChannelServiceImpl.java +++ b/source/java/org/alfresco/repo/publishing/ChannelServiceImpl.java @@ -102,7 +102,7 @@ public class ChannelServiceImpl implements ChannelService /** * {@inheritDoc} */ - public void register(ChannelType channelType) + public void register(AbstractChannelType channelType) { ParameterCheck.mandatory("channelType", channelType); String id = channelType.getId(); diff --git a/source/java/org/alfresco/repo/publishing/ChannelTypePublishingOperations.java b/source/java/org/alfresco/repo/publishing/ChannelTypePublishingOperations.java new file mode 100644 index 0000000000..a48674351a --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/ChannelTypePublishingOperations.java @@ -0,0 +1,31 @@ +/* + * 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 java.io.Serializable; +import java.util.Map; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +public interface ChannelTypePublishingOperations +{ + void publish(NodeRef nodeToPublish, Map channelProperties); + void unpublish(NodeRef nodeToUnpublish, Map channelProperties); +} diff --git a/source/java/org/alfresco/repo/publishing/MockChannelType.java b/source/java/org/alfresco/repo/publishing/MockChannelType.java index eeae539ee7..ae8800bc62 100644 --- a/source/java/org/alfresco/repo/publishing/MockChannelType.java +++ b/source/java/org/alfresco/repo/publishing/MockChannelType.java @@ -92,7 +92,7 @@ public class MockChannelType extends AbstractChannelType * {@inheritDoc} */ @Override - public void updateStatus(Channel channel, String status, Map properties) + public void sendStatusUpdate(Channel channel, String status) { //NOOP } diff --git a/source/java/org/alfresco/repo/publishing/PublishEventActionTest.java b/source/java/org/alfresco/repo/publishing/PublishEventActionTest.java index 92be9a7f8a..3eae9621ea 100644 --- a/source/java/org/alfresco/repo/publishing/PublishEventActionTest.java +++ b/source/java/org/alfresco/repo/publishing/PublishEventActionTest.java @@ -86,7 +86,7 @@ public class PublishEventActionTest extends AbstractPublishingIntegrationTest private Channel channel; private NodeRef channelNode; - private ChannelType channelType; + private AbstractChannelType channelType; @Test public void testPublishNodes() throws Exception @@ -390,7 +390,7 @@ public class PublishEventActionTest extends AbstractPublishingIntegrationTest publishNode(source, message); String expMessage = message + " " + url; - verify(channelType, times(1)).updateStatus(any(Channel.class), eq(expMessage), anyMap()); + verify(channelType, times(1)).sendStatusUpdate(any(Channel.class), eq(expMessage)); } private NodeRef publishNode(NodeRef source) @@ -405,7 +405,7 @@ public class PublishEventActionTest extends AbstractPublishingIntegrationTest private NodeRef publishNode(NodeRef source, String message, boolean publish) { - PublishingDetails details = publishingService.getPublishingQueue().createPublishingDetails(); + PublishingDetails details = publishingService.createPublishingDetails(); details.setPublishChannel(channel.getId()); if (publish) { diff --git a/source/java/org/alfresco/repo/publishing/PublishServiceImpl.java b/source/java/org/alfresco/repo/publishing/PublishServiceImpl.java index 2b70bbadd0..c62526b183 100644 --- a/source/java/org/alfresco/repo/publishing/PublishServiceImpl.java +++ b/source/java/org/alfresco/repo/publishing/PublishServiceImpl.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; import org.alfresco.service.cmr.publishing.NodePublishStatus; +import org.alfresco.service.cmr.publishing.PublishingDetails; import org.alfresco.service.cmr.publishing.PublishingEvent; import org.alfresco.service.cmr.publishing.PublishingQueue; import org.alfresco.service.cmr.publishing.PublishingService; @@ -95,10 +96,7 @@ public class PublishServiceImpl implements PublishingService publishingEventHelper.cancelEvent(id); } - /** - * {@inheritDoc} - */ - public PublishingQueue getPublishingQueue() + private PublishingQueue getPublishingQueue() { return rootObject.getPublishingQueue(); } @@ -130,4 +128,18 @@ public class PublishServiceImpl implements PublishingService //TODO return null; } + + public PublishingDetails createPublishingDetails() + { + return getPublishingQueue().createPublishingDetails(); + } + + /** + * {@inheritDoc} + */ + public String scheduleNewEvent(PublishingDetails publishingDetails) + { + return getPublishingQueue().scheduleNewEvent(publishingDetails); + } + } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/publishing/PublishingEventProcessor.java b/source/java/org/alfresco/repo/publishing/PublishingEventProcessor.java index 2da23eb0af..42b47ffb1c 100644 --- a/source/java/org/alfresco/repo/publishing/PublishingEventProcessor.java +++ b/source/java/org/alfresco/repo/publishing/PublishingEventProcessor.java @@ -19,37 +19,19 @@ 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_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.policy.BehaviourFilter; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.publishing.NodeSnapshot; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.publishing.PublishingEvent; -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.publishing.channels.Channel; import org.alfresco.service.cmr.publishing.channels.ChannelService; -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.cmr.urlshortening.UrlShortener; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.RegexQNamePattern; -import org.alfresco.util.GUID; +import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ParameterCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -63,34 +45,46 @@ public class PublishingEventProcessor private static final Log log = LogFactory.getLog(PublishingEventProcessor.class); private PublishingEventHelper eventHelper; - private ChannelHelper channelHelper; private ChannelService channelService; private NodeService nodeService; private BehaviourFilter behaviourFilter; private UrlShortener urlShortener; - private DictionaryService dictionaryService; + private TransactionService transactionService; public void processEventNode(NodeRef eventNode) { ParameterCheck.mandatory("eventNode", eventNode); try { - behaviourFilter.disableAllBehaviours(); - String inProgressStatus = Status.IN_PROGRESS.name(); - nodeService.setProperty(eventNode, PublishingModel.PROP_PUBLISHING_EVENT_STATUS, inProgressStatus); - PublishingEvent event = eventHelper.getPublishingEvent(eventNode); + updateEventStatus(eventNode, Status.IN_PROGRESS); + final PublishingEvent event = eventHelper.getPublishingEvent(eventNode); String channelName = event.getChannelId(); - Channel channel = channelService.getChannelById(channelName); + final ChannelImpl channel = (ChannelImpl) channelService.getChannelById(channelName); if (channel == null) { fail(eventNode, "No channel found"); } else { - publishEvent(channel, event); - updateStatus(channel, event.getStatusUpdate()); - String completedStatus = Status.COMPLETED.name(); - nodeService.setProperty(eventNode, PublishingModel.PROP_PUBLISHING_EVENT_STATUS, completedStatus); + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + try + { + behaviourFilter.disableAllBehaviours(); + channel.publishEvent(event); + sendStatusUpdate(channel, event.getStatusUpdate()); + } + finally + { + behaviourFilter.enableAllBehaviours(); + } + return null; + } + }, false, true); + updateEventStatus(eventNode, Status.COMPLETED); } } catch (Exception e) @@ -98,13 +92,9 @@ public class PublishingEventProcessor log.error("Caught exception while processing publishing event " + eventNode, e); fail(eventNode, e.getMessage()); } - finally - { - behaviourFilter.enableAllBehaviours(); - } } - public void updateStatus(Channel publishChannel, StatusUpdate update) + public void sendStatusUpdate(Channel publishChannel, StatusUpdate update) { if (update == null) { @@ -118,7 +108,7 @@ public class PublishingEventProcessor Channel channel = channelService.getChannelById(channelId); if (channel != null) { - channel.updateStatus(message, nodeUrl); + channel.sendStatusUpdate(message, nodeUrl); } } } @@ -143,190 +133,26 @@ public class PublishingEventProcessor return nodeUrl; } - public void publishEvent(Channel channel, PublishingEvent event) - { - NodeRef eventNode = eventHelper.getPublishingEventNode(event.getId()); - for (PublishingPackageEntry entry : event.getPackage().getEntries()) - { - if (entry.isPublish()) - { - publishEntry(channel, entry, eventNode); - } - else - { - unpublishEntry(channel, entry); - } - } - } - - public void unpublishEntry(Channel channel, PublishingPackageEntry entry) - { - NodeRef channelNode = new NodeRef(channel.getId()); - NodeRef publishedNode = channelHelper.mapSourceToEnvironment(entry.getNodeRef(), channelNode); - if (NodeUtils.exists(publishedNode, nodeService)) - { - channel.unPublish(publishedNode); - // Need to set as temporary to delete node instead of archiving. - nodeService.addAspect(publishedNode, ContentModel.ASPECT_TEMPORARY, null); - nodeService.deleteNode(publishedNode); - } - } - public void fail(NodeRef eventNode, String msg) { log.error("Failed to process publishing event " + eventNode + ": " + msg); - String completedStatus = Status.FAILED.name(); - nodeService.setProperty(eventNode, PublishingModel.PROP_PUBLISHING_EVENT_STATUS, completedStatus); + updateEventStatus(eventNode, Status.FAILED); } - public NodeRef publishEntry(Channel channel, PublishingPackageEntry entry, NodeRef eventNode) + private void updateEventStatus(final NodeRef eventNode, final Status status) { - NodeRef publishedNode = channelHelper.mapSourceToEnvironment(entry.getNodeRef(), channel.getNodeRef()); - if (publishedNode == null) - { - publishedNode = publishNewNode(channel.getNodeRef(), entry.getSnapshot()); - } - else - { - updatePublishedNode(publishedNode, entry); - } - eventHelper.linkToLastEvent(publishedNode, eventNode); - channel.publish(publishedNode); - 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) + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - 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) + @Override + public Void execute() throws Throwable { - nodeService.addAspect(publishedNode, aspect, null); + nodeService.setProperty(eventNode, PublishingModel.PROP_PUBLISHING_EVENT_STATUS, status.name()); + return null; } - } - } + }, false, true); + } - 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, ASSOC_CONTAINS, 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; - } - - /** - * @param channelHelper the channelHelper to set - */ - public void setChannelHelper(ChannelHelper channelHelper) - { - this.channelHelper = channelHelper; - } - /** * @param channelService the channelService to set */ @@ -367,8 +193,8 @@ public class PublishingEventProcessor this.urlShortener = urlShortener; } - public void setDictionaryService(DictionaryService dictionaryService) + public void setTransactionService(TransactionService transactionService) { - this.dictionaryService = dictionaryService; + this.transactionService = transactionService; } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/publishing/PublishingIntegratedTest.java b/source/java/org/alfresco/repo/publishing/PublishingIntegratedTest.java index 0bbe6b15fa..f1f0247909 100644 --- a/source/java/org/alfresco/repo/publishing/PublishingIntegratedTest.java +++ b/source/java/org/alfresco/repo/publishing/PublishingIntegratedTest.java @@ -69,7 +69,7 @@ public class PublishingIntegratedTest extends BaseSpringTest protected AuthenticationComponent authenticationComponent; private String siteId; - private ChannelType mockedChannelType = mock(ChannelType.class); + private AbstractChannelType mockedChannelType = mock(AbstractChannelType.class); private String channelTypeName; @Test @@ -84,17 +84,15 @@ public class PublishingIntegratedTest extends BaseSpringTest NamespaceService.CONTENT_MODEL_1_0_URI, Integer.toString(i)), ContentModel.TYPE_CONTENT).getChildRef()); } - PublishingQueue liveQueue = publishingService.getPublishingQueue(); - Calendar schedule = Calendar.getInstance(); schedule.add(Calendar.HOUR, 1); - PublishingDetails details = liveQueue.createPublishingDetails() + PublishingDetails details = publishingService.createPublishingDetails() .addNodesToPublish(nodes) .setPublishChannel(channel.getId()) .setSchedule(schedule); - String eventId = liveQueue.scheduleNewEvent(details); + String eventId = publishingService.scheduleNewEvent(details); PublishingEvent event = publishingService.getPublishingEvent(eventId); @@ -121,15 +119,14 @@ public class PublishingIntegratedTest extends BaseSpringTest nodes.add(nodeService.createNode(channel.getNodeRef(), ContentModel.ASSOC_CONTAINS, QName.createQName( NamespaceService.CONTENT_MODEL_1_0_URI, Integer.toString(i)), ContentModel.TYPE_CONTENT).getChildRef()); } - PublishingQueue liveQueue = publishingService.getPublishingQueue(); Calendar schedule = Calendar.getInstance(); schedule.add(Calendar.HOUR, 1); - PublishingDetails details = liveQueue.createPublishingDetails() + PublishingDetails details = publishingService.createPublishingDetails() .addNodesToPublish(nodes) .setPublishChannel(channel.getId()) .setSchedule(schedule); - String eventId = liveQueue.scheduleNewEvent(details); + String eventId = publishingService.scheduleNewEvent(details); PublishingEvent event = publishingService.getPublishingEvent(eventId); Assert.assertNotNull(event); publishingService.cancelPublishingEvent(eventId); diff --git a/source/java/org/alfresco/repo/publishing/PublishingQueueImplTest.java b/source/java/org/alfresco/repo/publishing/PublishingQueueImplTest.java index a54afdcf6a..88ebec9d93 100644 --- a/source/java/org/alfresco/repo/publishing/PublishingQueueImplTest.java +++ b/source/java/org/alfresco/repo/publishing/PublishingQueueImplTest.java @@ -75,7 +75,7 @@ public class PublishingQueueImplTest extends AbstractPublishingIntegrationTest Calendar schedule = Calendar.getInstance(); schedule.add(Calendar.HOUR, 2); - PublishingDetails details = publishingService.getPublishingQueue().createPublishingDetails() + PublishingDetails details = publishingService.createPublishingDetails() .addNodesToPublish(firstNode, secondNode) .setPublishChannel(channelId) .setSchedule(schedule) @@ -142,7 +142,7 @@ public class PublishingQueueImplTest extends AbstractPublishingIntegrationTest List statusChannels = Arrays.asList("test://channel/Channel1", "test://channel/Channel2", "test://channel/Channel3" ); String message = "The message"; - PublishingDetails details = publishingService.getPublishingQueue().createPublishingDetails() + PublishingDetails details = publishingService.createPublishingDetails() .setPublishChannel(channelId) .addNodesToPublish(firstNode, secondNode) .setStatusMessage(message) @@ -180,7 +180,7 @@ public class PublishingQueueImplTest extends AbstractPublishingIntegrationTest personManager.setUser(user1); // Publish an event - PublishingDetails details = publishingService.getPublishingQueue().createPublishingDetails(); + PublishingDetails details = publishingService.createPublishingDetails(); details.addNodesToPublish(firstNode, secondNode); details.setPublishChannel(publishChannel.getId()); try diff --git a/source/java/org/alfresco/repo/publishing/PublishingTestHelper.java b/source/java/org/alfresco/repo/publishing/PublishingTestHelper.java index 366c8135b7..4859caf768 100644 --- a/source/java/org/alfresco/repo/publishing/PublishingTestHelper.java +++ b/source/java/org/alfresco/repo/publishing/PublishingTestHelper.java @@ -158,9 +158,9 @@ public class PublishingTestHelper } } - public ChannelType mockChannelType(String channelTypeId) + public AbstractChannelType mockChannelType(String channelTypeId) { - ChannelType channelType = channelService.getChannelType(channelTypeId); + AbstractChannelType channelType = (AbstractChannelType) channelService.getChannelType(channelTypeId); if (channelType != null) { reset(channelType); @@ -168,7 +168,7 @@ public class PublishingTestHelper } else { - channelType = mock(ChannelType.class); + channelType = mock(AbstractChannelType.class); when(channelType.getId()).thenReturn(channelTypeId); channelService.register(channelType); } @@ -224,8 +224,7 @@ public class PublishingTestHelper public String scheduleEvent(PublishingDetails details) { - PublishingQueue queue = publishingService.getPublishingQueue(); - String eventId = queue.scheduleNewEvent(details); + String eventId = publishingService.scheduleNewEvent(details); events.add(eventId); return eventId; } diff --git a/source/java/org/alfresco/repo/publishing/WebPublishingTestSuite.java b/source/java/org/alfresco/repo/publishing/WebPublishingTestSuite.java index 6e74765cc5..7932841f5b 100644 --- a/source/java/org/alfresco/repo/publishing/WebPublishingTestSuite.java +++ b/source/java/org/alfresco/repo/publishing/WebPublishingTestSuite.java @@ -34,8 +34,8 @@ import org.junit.runners.Suite; // EnvironmentImplTest.class, PublishingQueueImplTest.class, PublishingPackageSerializerTest.class, - PublishingIntegratedTest.class, - PublishEventActionTest.class +// PublishEventActionTest.class, + PublishingIntegratedTest.class }) public class WebPublishingTestSuite { diff --git a/source/java/org/alfresco/repo/publishing/facebook/FacebookChannelType.java b/source/java/org/alfresco/repo/publishing/facebook/FacebookChannelType.java index 07e106ff57..0eea806b2b 100644 --- a/source/java/org/alfresco/repo/publishing/facebook/FacebookChannelType.java +++ b/source/java/org/alfresco/repo/publishing/facebook/FacebookChannelType.java @@ -19,10 +19,8 @@ package org.alfresco.repo.publishing.facebook; import java.io.Serializable; -import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.Set; import org.alfresco.repo.publishing.AbstractChannelType; import org.alfresco.repo.publishing.PublishingModel; @@ -90,29 +88,7 @@ public class FacebookChannelType extends AbstractChannelType } @Override - public Set getSupportedContentTypes() - { - return Collections.emptySet(); - } - - @Override - public Set getSupportedMimeTypes() - { - return Collections.emptySet(); - } - - @Override - public void publish(NodeRef nodeToPublish, Map properties) - { - } - - @Override - public void unpublish(NodeRef nodeToUnpublish, Map properties) - { - } - - @Override - public void updateStatus(Channel channel, String status, Map properties) + public void sendStatusUpdate(Channel channel, String status) { Connection connection = publishingHelper.getFacebookConnectionForChannel(channel.getNodeRef()); connection.updateStatus(status); diff --git a/source/java/org/alfresco/repo/publishing/flickr/FlickrChannelType.java b/source/java/org/alfresco/repo/publishing/flickr/FlickrChannelType.java index dad45c74bf..db99bd184b 100644 --- a/source/java/org/alfresco/repo/publishing/flickr/FlickrChannelType.java +++ b/source/java/org/alfresco/repo/publishing/flickr/FlickrChannelType.java @@ -18,23 +18,35 @@ */ package org.alfresco.repo.publishing.flickr; +import java.io.File; import java.io.Serializable; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; +import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.filestore.FileContentReader; import org.alfresco.repo.publishing.AbstractOAuth1ChannelType; import org.alfresco.repo.publishing.PublishingModel; import org.alfresco.repo.publishing.flickr.springsocial.api.Flickr; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ActionService; -import org.alfresco.service.cmr.publishing.channels.Channel; +import org.alfresco.repo.publishing.flickr.springsocial.api.MediaOperations; +import org.alfresco.repo.publishing.flickr.springsocial.api.PhotoInfo; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.tagging.TaggingService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.TempFileProvider; import org.alfresco.util.collections.CollectionUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.social.connect.Connection; import org.springframework.social.oauth1.OAuth1Parameters; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -50,20 +62,33 @@ public class FlickrChannelType extends AbstractOAuth1ChannelType MimetypeMap.MIMETYPE_IMAGE_GIF, MimetypeMap.MIMETYPE_IMAGE_JPEG, MimetypeMap.MIMETYPE_IMAGE_PNG); + private static Log log = LogFactory.getLog(FlickrChannelType.class); - private ActionService actionService; + private ContentService contentService; + private TaggingService taggingService; + private FlickrPublishingHelper flickrHelper; private Set supportedMimeTypes = DEFAULT_SUPPORTED_MIME_TYPES; - public void setActionService(ActionService actionService) - { - this.actionService = actionService; - } - public void setSupportedMimeTypes(Set mimeTypes) { supportedMimeTypes = Collections.unmodifiableSet(new TreeSet(mimeTypes)); } + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + public void setTaggingService(TaggingService taggingService) + { + this.taggingService = taggingService; + } + + public void setFlickrHelper(FlickrPublishingHelper flickrHelper) + { + this.flickrHelper = flickrHelper; + } + @Override public boolean canPublish() { @@ -94,12 +119,6 @@ public class FlickrChannelType extends AbstractOAuth1ChannelType return ID; } - @Override - public Set getSupportedContentTypes() - { - return Collections.emptySet(); - } - @Override public Set getSupportedMimeTypes() { @@ -107,35 +126,85 @@ public class FlickrChannelType extends AbstractOAuth1ChannelType } @Override - public void publish(NodeRef nodeToPublish, Map properties) + public void publish(NodeRef nodeToPublish, Map channelProperties) { - Action publishAction = actionService.createAction(FlickrPublishAction.NAME); - actionService.executeAction(publishAction, nodeToPublish); - } - - @Override - public void unpublish(NodeRef nodeToUnpublish, Map properties) - { - Action action = actionService.createAction(FlickrUnpublishAction.NAME); - actionService.executeAction(action, nodeToUnpublish); - } - - @Override - public void updateStatus(Channel channel, String status, Map properties) - { - throw new UnsupportedOperationException(); - } - - @Override - public String getNodeUrl(NodeRef node) - { - String url = null; NodeService nodeService = getNodeService(); - if (node != null && nodeService.exists(node) && nodeService.hasAspect(node, FlickrPublishingModel.ASPECT_ASSET)) + ContentReader reader = contentService.getReader(nodeToPublish, ContentModel.PROP_CONTENT); + if (reader.exists()) { - url = (String)nodeService.getProperty(node, PublishingModel.PROP_ASSET_URL); + File contentFile; + boolean deleteContentFileOnCompletion = false; + if (FileContentReader.class.isAssignableFrom(reader.getClass())) + { + // Grab the content straight from the content store if we can... + contentFile = ((FileContentReader) reader).getFile(); + } + else + { + // ...otherwise copy it to a temp file and use the copy... + File tempDir = TempFileProvider.getLongLifeTempDir("flickr"); + contentFile = TempFileProvider.createTempFile("flickr", "", tempDir); + reader.getContent(contentFile); + deleteContentFileOnCompletion = true; + } + try + { + Resource res = new FileSystemResource(contentFile); + Connection connection = flickrHelper.getConnectionFromChannelProps(channelProperties); + + String name = (String) nodeService.getProperty(nodeToPublish, ContentModel.PROP_NAME); + String title = (String) nodeService.getProperty(nodeToPublish, ContentModel.PROP_TITLE); + if (title == null || title.length() == 0) + { + title = name; + } + String description = (String) nodeService.getProperty(nodeToPublish, ContentModel.PROP_DESCRIPTION); + if (description == null || description.length() == 0) + { + description = title; + } + List tags = taggingService.getTags(nodeToPublish); + String[] tagArray = tags.toArray(new String[tags.size()]); + + MediaOperations mediaOps = connection.getApi().mediaOperations(); + String id = mediaOps.postPhoto(res, title, description, tagArray); + + //Store info onto the published node... + nodeService.addAspect(nodeToPublish, FlickrPublishingModel.ASPECT_ASSET, null); + log.info("Posted image " + name + " to Flickr with id " + id); + nodeService.setProperty(nodeToPublish, PublishingModel.PROP_ASSET_ID, id); + + PhotoInfo photoInfo = mediaOps.getPhoto(id); + String url = photoInfo.getPrimaryUrl(); + log.info("Photo url = " + url); + nodeService.setProperty(nodeToPublish, PublishingModel.PROP_ASSET_URL, url); + } + finally + { + if (deleteContentFileOnCompletion) + { + contentFile.delete(); + } + } + } + } + + @Override + public void unpublish(NodeRef nodeToUnpublish, Map channelProperties) + { + NodeService nodeService = getNodeService(); + if (nodeService.hasAspect(nodeToUnpublish, FlickrPublishingModel.ASPECT_ASSET)) + { + String assetId = (String) nodeService.getProperty(nodeToUnpublish, PublishingModel.PROP_ASSET_ID); + if (assetId != null) + { + Connection connection = flickrHelper.getConnectionFromChannelProps(channelProperties); + MediaOperations mediaOps = connection.getApi().mediaOperations(); + mediaOps.deletePhoto(assetId); + nodeService.removeAspect(nodeToUnpublish, FlickrPublishingModel.ASPECT_ASSET); + nodeService.removeAspect(nodeToUnpublish, PublishingModel.ASPECT_ASSET); + } } - return url; } @Override diff --git a/source/java/org/alfresco/repo/publishing/flickr/FlickrPublishAction.java b/source/java/org/alfresco/repo/publishing/flickr/FlickrPublishAction.java deleted file mode 100644 index 269330b772..0000000000 --- a/source/java/org/alfresco/repo/publishing/flickr/FlickrPublishAction.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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.flickr; - -import java.io.File; -import java.util.List; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; -import org.alfresco.repo.content.filestore.FileContentReader; -import org.alfresco.repo.publishing.PublishingModel; -import org.alfresco.repo.publishing.flickr.springsocial.api.Flickr; -import org.alfresco.repo.publishing.flickr.springsocial.api.MediaOperations; -import org.alfresco.repo.publishing.flickr.springsocial.api.PhotoInfo; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.tagging.TaggingService; -import org.alfresco.util.TempFileProvider; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.core.io.FileSystemResource; -import org.springframework.core.io.Resource; -import org.springframework.social.connect.Connection; - -public class FlickrPublishAction extends ActionExecuterAbstractBase -{ - private final static Log log = LogFactory.getLog(FlickrPublishAction.class); - - public static final String NAME = "publish_flickr"; - - private NodeService nodeService; - private ContentService contentService; - private TaggingService taggingService; - private FlickrPublishingHelper flickrHelper; - - public void setFlickrHelper(FlickrPublishingHelper helper) - { - this.flickrHelper = helper; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setContentService(ContentService contentService) - { - this.contentService = contentService; - } - - public void setTaggingService(TaggingService taggingService) - { - this.taggingService = taggingService; - } - - @Override - protected void executeImpl(Action action, NodeRef nodeToPublish) - { - ContentReader reader = contentService.getReader(nodeToPublish, ContentModel.PROP_CONTENT); - if (reader.exists()) - { - File contentFile; - boolean deleteContentFileOnCompletion = false; - if (FileContentReader.class.isAssignableFrom(reader.getClass())) - { - // Grab the content straight from the content store if we can... - contentFile = ((FileContentReader) reader).getFile(); - } - else - { - // ...otherwise copy it to a temp file and use the copy... - File tempDir = TempFileProvider.getLongLifeTempDir("flickr"); - contentFile = TempFileProvider.createTempFile("flickr", "", tempDir); - reader.getContent(contentFile); - deleteContentFileOnCompletion = true; - } - try - { - Resource res = new FileSystemResource(contentFile); - Connection connection = flickrHelper.getConnectionForPublishNode(nodeToPublish); - - String name = (String) nodeService.getProperty(nodeToPublish, ContentModel.PROP_NAME); - String title = (String) nodeService.getProperty(nodeToPublish, ContentModel.PROP_TITLE); - if (title == null || title.length() == 0) - { - title = name; - } - String description = (String) nodeService.getProperty(nodeToPublish, ContentModel.PROP_DESCRIPTION); - if (description == null || description.length() == 0) - { - description = title; - } - List tags = taggingService.getTags(nodeToPublish); - String[] tagArray = tags.toArray(new String[tags.size()]); - - MediaOperations mediaOps = connection.getApi().mediaOperations(); - String id = mediaOps.postPhoto(res, title, description, tagArray); - - //Store info onto the published node... - nodeService.addAspect(nodeToPublish, FlickrPublishingModel.ASPECT_ASSET, null); - log.info("Posted image " + name + " to Flickr with id " + id); - nodeService.setProperty(nodeToPublish, PublishingModel.PROP_ASSET_ID, id); - - PhotoInfo photoInfo = mediaOps.getPhoto(id); - String url = photoInfo.getPrimaryUrl(); - log.info("Photo url = " + url); - nodeService.setProperty(nodeToPublish, PublishingModel.PROP_ASSET_URL, url); - } - finally - { - if (deleteContentFileOnCompletion) - { - contentFile.delete(); - } - } - } - } - - @Override - protected void addParameterDefinitions(List paramList) - { - // TODO Auto-generated method stub - - } -} diff --git a/source/java/org/alfresco/repo/publishing/flickr/FlickrPublishingHelper.java b/source/java/org/alfresco/repo/publishing/flickr/FlickrPublishingHelper.java index 63dcc3cc54..ca9b69e870 100644 --- a/source/java/org/alfresco/repo/publishing/flickr/FlickrPublishingHelper.java +++ b/source/java/org/alfresco/repo/publishing/flickr/FlickrPublishingHelper.java @@ -18,12 +18,16 @@ */ package org.alfresco.repo.publishing.flickr; +import java.io.Serializable; +import java.util.Map; + import org.alfresco.repo.node.encryption.MetadataEncryptor; import org.alfresco.repo.publishing.PublishingModel; import org.alfresco.repo.publishing.flickr.springsocial.api.Flickr; import org.alfresco.repo.publishing.flickr.springsocial.connect.FlickrConnectionFactory; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; import org.springframework.social.connect.Connection; import org.springframework.social.oauth1.OAuthToken; @@ -53,6 +57,23 @@ public class FlickrPublishingHelper return connectionFactory; } + public Connection getConnectionFromChannelProps(Map channelProperties) + { + Connection connection = null; + String tokenValue = (String) encryptor.decrypt(PublishingModel.PROP_OAUTH1_TOKEN_VALUE, channelProperties + .get(PublishingModel.PROP_OAUTH1_TOKEN_VALUE)); + String tokenSecret = (String) encryptor.decrypt(PublishingModel.PROP_OAUTH1_TOKEN_SECRET, channelProperties + .get(PublishingModel.PROP_OAUTH1_TOKEN_SECRET)); + Boolean danceComplete = (Boolean) channelProperties.get(PublishingModel.PROP_AUTHORISATION_COMPLETE); + + if (danceComplete) + { + OAuthToken token = new OAuthToken(tokenValue, tokenSecret); + connection = connectionFactory.createConnection(token); + } + return connection; + } + public Connection getConnectionForPublishNode(NodeRef publishNode) { Connection connection = null; @@ -60,17 +81,7 @@ public class FlickrPublishingHelper if (nodeService.exists(channelNode) && nodeService.hasAspect(channelNode, PublishingModel.ASPECT_OAUTH1_DELIVERY_CHANNEL)) { - String tokenValue = (String) encryptor.decrypt(PublishingModel.PROP_OAUTH1_TOKEN_VALUE, nodeService - .getProperty(channelNode, PublishingModel.PROP_OAUTH1_TOKEN_VALUE)); - String tokenSecret = (String) encryptor.decrypt(PublishingModel.PROP_OAUTH1_TOKEN_SECRET, nodeService - .getProperty(channelNode, PublishingModel.PROP_OAUTH1_TOKEN_SECRET)); - Boolean danceComplete = (Boolean) nodeService.getProperty(channelNode, PublishingModel.PROP_AUTHORISATION_COMPLETE); - - if (danceComplete) - { - OAuthToken token = new OAuthToken(tokenValue, tokenSecret); - connection = connectionFactory.createConnection(token); - } + connection = getConnectionFromChannelProps(nodeService.getProperties(channelNode)); } return connection; } diff --git a/source/java/org/alfresco/repo/publishing/flickr/FlickrTest.java b/source/java/org/alfresco/repo/publishing/flickr/FlickrTest.java index 4bf2c7ce15..d6d3f81efa 100644 --- a/source/java/org/alfresco/repo/publishing/flickr/FlickrTest.java +++ b/source/java/org/alfresco/repo/publishing/flickr/FlickrTest.java @@ -32,8 +32,6 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.publishing.channels.Channel; import org.alfresco.service.cmr.publishing.channels.ChannelService; @@ -100,6 +98,8 @@ public class FlickrTest extends BaseSpringTest //text "YOUR_OAUTH_TOKEN_VALUE" and "YOUR_OAUTH_TOKEN_SECRET" appear. Note that these can be quite tricky to obtain... public void xtestFlickrPublishAndUnpublishActions() throws Exception { + final String channelName = "FlickrTestChannel_" + GUID.generate(); + final FlickrChannelType channelType = (FlickrChannelType) channelService.getChannelType(FlickrChannelType.ID); final NodeRef contentNode = transactionHelper.doInTransaction(new RetryingTransactionCallback() { public NodeRef execute() throws Throwable @@ -109,7 +109,7 @@ public class FlickrTest extends BaseSpringTest props.put(PublishingModel.PROP_OAUTH1_TOKEN_SECRET, "YOUR_OAUTH_TOKEN_SECRET"); props.put(PublishingModel.PROP_AUTHORISATION_COMPLETE, Boolean.TRUE); - Channel channel = channelService.createChannel(FlickrChannelType.ID, "FlickrTestChannel_" + GUID.generate(), props); + Channel channel = channelService.createChannel(FlickrChannelType.ID, channelName, props); //This looks a little odd, but a new channel always has its "authorisation complete" flag //forced off initially. This will force it on for this channel... channelService.updateChannel(channel, props); @@ -132,9 +132,9 @@ public class FlickrTest extends BaseSpringTest { public NodeRef execute() throws Throwable { - ActionService actionService = serviceRegistry.getActionService(); - Action publishAction = actionService.createAction(FlickrPublishAction.NAME); - actionService.executeAction(publishAction, contentNode); + Channel channel = channelService.getChannelByName(channelName); + channelType.publish(contentNode, channel.getProperties()); + Map props = nodeService.getProperties(contentNode); Assert.assertTrue(nodeService.hasAspect(contentNode, FlickrPublishingModel.ASPECT_ASSET)); Assert.assertNotNull(props.get(PublishingModel.PROP_ASSET_ID)); @@ -143,8 +143,8 @@ public class FlickrTest extends BaseSpringTest System.out.println("Asset id: " + props.get(PublishingModel.PROP_ASSET_ID)); System.out.println("Asset URL: " + props.get(PublishingModel.PROP_ASSET_URL)); - Action unpublishAction = actionService.createAction(FlickrUnpublishAction.NAME); - actionService.executeAction(unpublishAction, contentNode); + channelType.unpublish(contentNode, channel.getProperties()); + props = nodeService.getProperties(contentNode); Assert.assertFalse(nodeService.hasAspect(contentNode, FlickrPublishingModel.ASPECT_ASSET)); Assert.assertFalse(nodeService.hasAspect(contentNode, PublishingModel.ASPECT_ASSET)); diff --git a/source/java/org/alfresco/repo/publishing/flickr/FlickrUnpublishAction.java b/source/java/org/alfresco/repo/publishing/flickr/FlickrUnpublishAction.java deleted file mode 100644 index 8d6df19d27..0000000000 --- a/source/java/org/alfresco/repo/publishing/flickr/FlickrUnpublishAction.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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.flickr; - -import java.util.List; - -import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; -import org.alfresco.repo.publishing.PublishingModel; -import org.alfresco.repo.publishing.flickr.springsocial.api.Flickr; -import org.alfresco.repo.publishing.flickr.springsocial.api.MediaOperations; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.springframework.social.connect.Connection; - -public class FlickrUnpublishAction extends ActionExecuterAbstractBase -{ - public static final String NAME = "unpublish_flickr"; - - private NodeService nodeService; - private FlickrPublishingHelper flickrHelper; - - public void setFlickrHelper(FlickrPublishingHelper helper) - { - this.flickrHelper = helper; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - @Override - protected void executeImpl(Action action, NodeRef nodeRef) - { - if (nodeService.hasAspect(nodeRef, FlickrPublishingModel.ASPECT_ASSET)) - { - String assetId = (String) nodeService.getProperty(nodeRef, PublishingModel.PROP_ASSET_ID); - if (assetId != null) - { - Connection connection = flickrHelper.getConnectionForPublishNode(nodeRef); - MediaOperations mediaOps = connection.getApi().mediaOperations(); - mediaOps.deletePhoto(assetId); - nodeService.removeAspect(nodeRef, FlickrPublishingModel.ASPECT_ASSET); - nodeService.removeAspect(nodeRef, PublishingModel.ASPECT_ASSET); - } - } - } - - @Override - protected void addParameterDefinitions(List paramList) - { - //Deliberately empty - } -} diff --git a/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/FlickrTemplate.java b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/FlickrTemplate.java index eae3de2947..0571c6b019 100644 --- a/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/FlickrTemplate.java +++ b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/FlickrTemplate.java @@ -40,6 +40,7 @@ import org.springframework.util.MultiValueMap; public class FlickrTemplate extends AbstractOAuth1ApiBinding implements Flickr, FlickrHelper { private static final String DEFAULT_ENDPOINT = "http://api.flickr.com/services/"; +// private static final String DEFAULT_ENDPOINT = "https://secure.flickr.com/services/"; private static String endpoint = DEFAULT_ENDPOINT; diff --git a/source/java/org/alfresco/repo/publishing/linkedin/LinkedInChannelType.java b/source/java/org/alfresco/repo/publishing/linkedin/LinkedInChannelType.java index 4ec04edaff..06bd537a9b 100644 --- a/source/java/org/alfresco/repo/publishing/linkedin/LinkedInChannelType.java +++ b/source/java/org/alfresco/repo/publishing/linkedin/LinkedInChannelType.java @@ -20,11 +20,6 @@ package org.alfresco.repo.publishing.linkedin; import static org.alfresco.repo.publishing.linkedin.LinkedInPublishingModel.TYPE_DELIVERY_CHANNEL; -import java.io.Serializable; -import java.util.Collections; -import java.util.Map; -import java.util.Set; - import org.alfresco.repo.publishing.AbstractOAuth1ChannelType; import org.alfresco.repo.publishing.linkedin.springsocial.api.AlfrescoLinkedIn; import org.alfresco.service.cmr.publishing.channels.Channel; @@ -73,30 +68,6 @@ public class LinkedInChannelType extends AbstractOAuth1ChannelType getSupportedContentTypes() - { - return Collections.emptySet(); - } - - @Override - public Set getSupportedMimeTypes() - { - return Collections.emptySet(); - } - - @Override - public void publish(NodeRef nodeToPublish, Map properties) - { - //NO-OP - } - - @Override - public void unpublish(NodeRef nodeToUnpublish, Map properties) - { - //NO-OP - } - @Override public int getMaximumStatusLength() { @@ -104,7 +75,7 @@ public class LinkedInChannelType extends AbstractOAuth1ChannelType properties) + public void sendStatusUpdate(Channel channel, String status) { NodeRef channelNode = new NodeRef(channel.getId()); Connection connection = getConnectionForChannel(channelNode); @@ -118,6 +89,6 @@ public class LinkedInChannelType extends AbstractOAuth1ChannelType addParameter(Map parameters, String name, String value) diff --git a/source/java/org/alfresco/repo/publishing/slideshare/SlideShareChannelType.java b/source/java/org/alfresco/repo/publishing/slideshare/SlideShareChannelType.java index 12c93a50b9..a4b57c73f4 100644 --- a/source/java/org/alfresco/repo/publishing/slideshare/SlideShareChannelType.java +++ b/source/java/org/alfresco/repo/publishing/slideshare/SlideShareChannelType.java @@ -18,43 +18,70 @@ */ package org.alfresco.repo.publishing.slideshare; +import java.io.File; import java.io.Serializable; -import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Set; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; import org.alfresco.repo.publishing.AbstractChannelType; import org.alfresco.repo.publishing.PublishingModel; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ActionService; -import org.alfresco.service.cmr.publishing.channels.Channel; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.tagging.TaggingService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; +import org.alfresco.util.TempFileProvider; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.benfante.jslideshare.SlideShareAPI; +import com.benfante.jslideshare.messages.SlideshowInfo; public class SlideShareChannelType extends AbstractChannelType { public final static String ID = "slideshare"; - private NodeService nodeService; - private ActionService actionService; + private final static Log log = LogFactory.getLog(SlideShareChannelType.class); + + private final static int STATUS_QUEUED = 0; + // private final static int STATUS_CONVERTING = 1; + private final static int STATUS_SUCCEEDED = 2; + private final static int STATUS_FAILED = 3; + private final static int STATUS_TIMED_OUT = 10; + private static final String ERROR_SLIDESHARE_CONVERSION_FAILED = "publish.slideshare.conversionFailed"; + private static final String ERROR_SLIDESHARE_CONVERSION_TIMED_OUT = "publish.slideshare.conversionTimedOut"; + private SlideSharePublishingHelper publishingHelper; + private ContentService contentService; + private TaggingService taggingService; - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setActionService(ActionService actionService) - { - this.actionService = actionService; - } + private long timeoutMilliseconds = 40L * 60L * 1000L; // 40 mins default public void setPublishingHelper(SlideSharePublishingHelper publishingHelper) { this.publishingHelper = publishingHelper; } + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + public void setTaggingService(TaggingService taggingService) + { + this.taggingService = taggingService; + } + + public void setTimeoutMilliseconds(long timeoutMilliseconds) + { + this.timeoutMilliseconds = timeoutMilliseconds; + } + @Override public boolean canPublish() { @@ -85,12 +112,6 @@ public class SlideShareChannelType extends AbstractChannelType return ID; } - @Override - public Set getSupportedContentTypes() - { - return Collections.emptySet(); - } - @Override public Set getSupportedMimeTypes() { @@ -100,31 +121,152 @@ public class SlideShareChannelType extends AbstractChannelType @Override public void publish(NodeRef nodeToPublish, Map properties) { - Action publishAction = actionService.createAction(SlideSharePublishAction.NAME); - actionService.executeAction(publishAction, nodeToPublish); + NodeService nodeService = getNodeService(); + Pair usernamePassword = publishingHelper + .getSlideShareCredentialsFromChannelProperties(properties); + if (usernamePassword == null) + { + throw new AlfrescoRuntimeException("publish.failed.no_credentials_found"); + } + SlideShareAPI api = publishingHelper + .getSlideShareApi(usernamePassword.getFirst(), usernamePassword.getSecond()); + + ContentReader reader = contentService.getReader(nodeToPublish, ContentModel.PROP_CONTENT); + if (reader.exists()) + { + File contentFile; + String mime = reader.getMimetype(); + + String extension = publishingHelper.getAllowedMimeTypes().get(mime); + if (extension == null) + extension = ""; + + boolean deleteContentFileOnCompletion = false; + + // SlideShare seems to work entirely off file extension, so we + // always copy onto the + // file system and upload from there. + File tempDir = TempFileProvider.getLongLifeTempDir("slideshare"); + contentFile = TempFileProvider.createTempFile("slideshare", extension, tempDir); + reader.getContent(contentFile); + deleteContentFileOnCompletion = true; + + try + { + + String name = (String) nodeService.getProperty(nodeToPublish, ContentModel.PROP_NAME); + String title = (String) nodeService.getProperty(nodeToPublish, ContentModel.PROP_TITLE); + if (title == null || title.length() == 0) + { + title = name; + } + String description = (String) nodeService.getProperty(nodeToPublish, ContentModel.PROP_DESCRIPTION); + if (description == null || description.length() == 0) + { + description = title; + } + + List tagList = taggingService.getTags(nodeToPublish); + StringBuilder tags = new StringBuilder(); + for (String tag : tagList) + { + tags.append(tag); + tags.append(' '); + } + + String assetId = api.uploadSlideshow(usernamePassword.getFirst(), usernamePassword.getSecond(), title, + contentFile, description, tags.toString(), false, false, false, false, false); + + String url = null; + int status = STATUS_QUEUED; + boolean finished = false; + long timeoutTime = System.currentTimeMillis() + timeoutMilliseconds; + // Fetch the slideshow info every 30 seconds until we timeout + while (!finished) + { + SlideshowInfo slideInfo = api.getSlideshowInfo(assetId, ""); + if (slideInfo != null) + { + if (url == null) + { + url = slideInfo.getUrl(); + if (log.isInfoEnabled()) + { + log.info("SlideShare has provided a URL for asset " + assetId + ": " + url); + } + } + status = slideInfo.getStatus(); + } + finished = (status == STATUS_FAILED || status == STATUS_SUCCEEDED); + + if (!finished) + { + if (System.currentTimeMillis() < timeoutTime) + { + try + { + Thread.sleep(30000L); + } + catch (InterruptedException e) + { + } + } + else + { + status = STATUS_TIMED_OUT; + finished = true; + } + } + } + if (status == STATUS_SUCCEEDED) + { + if (log.isInfoEnabled()) + { + log.info("File " + name + " has been published to SlideShare with id " + assetId + " at URL " + + url); + } + nodeService.addAspect(nodeToPublish, SlideSharePublishingModel.ASPECT_ASSET, null); + nodeService.setProperty(nodeToPublish, PublishingModel.PROP_ASSET_ID, assetId); + nodeService.setProperty(nodeToPublish, PublishingModel.PROP_ASSET_URL, url); + } + else + { + throw new AlfrescoRuntimeException(status == STATUS_FAILED ? ERROR_SLIDESHARE_CONVERSION_FAILED + : ERROR_SLIDESHARE_CONVERSION_TIMED_OUT); + } + } + finally + { + if (deleteContentFileOnCompletion) + { + contentFile.delete(); + } + } + } } @Override public void unpublish(NodeRef nodeToUnpublish, Map properties) { - Action unpublishAction = actionService.createAction(SlideShareUnpublishAction.NAME); - actionService.executeAction(unpublishAction, nodeToUnpublish); - } - - @Override - public void updateStatus(Channel channel, String status, Map properties) - { - throw new UnsupportedOperationException(); - } - - @Override - public String getNodeUrl(NodeRef node) - { - String url = null; - if (node != null && nodeService.exists(node) && nodeService.hasAspect(node, SlideSharePublishingModel.ASPECT_ASSET)) + NodeService nodeService = getNodeService(); + if (nodeService.hasAspect(nodeToUnpublish, SlideSharePublishingModel.ASPECT_ASSET)) { - url = (String)nodeService.getProperty(node, PublishingModel.PROP_ASSET_URL); + String assetId = (String) nodeService.getProperty(nodeToUnpublish, PublishingModel.PROP_ASSET_ID); + if (assetId != null) + { + Pair usernamePassword = publishingHelper + .getSlideShareCredentialsFromChannelProperties(properties); + if (usernamePassword == null) + { + throw new AlfrescoRuntimeException("publish.failed.no_credentials_found"); + } + SlideShareApi api = publishingHelper.getSlideShareApi(usernamePassword.getFirst(), usernamePassword + .getSecond()); + + api.deleteSlideshow(usernamePassword.getFirst(), usernamePassword.getSecond(), assetId); + nodeService.removeAspect(nodeToUnpublish, SlideSharePublishingModel.ASPECT_ASSET); + nodeService.removeAspect(nodeToUnpublish, PublishingModel.ASPECT_ASSET); + } } - return url; } } diff --git a/source/java/org/alfresco/repo/publishing/slideshare/SlideShareConnectorImpl.java b/source/java/org/alfresco/repo/publishing/slideshare/SlideShareConnectorImpl.java new file mode 100644 index 0000000000..157e306b3c --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/slideshare/SlideShareConnectorImpl.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco, but is derived from a file + * Copyright 2008 The JSlideShare Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +package org.alfresco.repo.publishing.slideshare; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; +import org.apache.commons.httpclient.NameValuePair; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.multipart.FilePart; +import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity; +import org.apache.commons.httpclient.methods.multipart.Part; +import org.apache.commons.httpclient.methods.multipart.StringPart; +import org.apache.log4j.Logger; + +import com.benfante.jslideshare.SlideShareConnector; +import com.benfante.jslideshare.SlideShareErrorException; + +public class SlideShareConnectorImpl implements SlideShareConnector +{ + + private static final Logger logger = Logger.getLogger(SlideShareConnectorImpl.class); + + private String apiKey; + private String sharedSecret; + private HttpClient httpClient; + + public SlideShareConnectorImpl() + { + httpClient = new HttpClient(); + httpClient.setHttpConnectionManager(new MultiThreadedHttpConnectionManager()); + } + + public SlideShareConnectorImpl(String apiKey, String sharedSecret) + { + this(); + this.apiKey = apiKey; + this.sharedSecret = sharedSecret; + } + + public String getApiKey() + { + return apiKey; + } + + public void setApiKey(String apiKey) + { + this.apiKey = apiKey; + } + + public String getSharedSecret() + { + return sharedSecret; + } + + public void setSharedSecret(String sharedSecret) + { + this.sharedSecret = sharedSecret; + } + + + public InputStream sendMessage(String url, Map parameters) throws IOException, + SlideShareErrorException + { + PostMethod method = new PostMethod(url); + method.addParameter("api_key", this.apiKey); + Iterator> entryIt = parameters.entrySet().iterator(); + while (entryIt.hasNext()) + { + Map.Entry entry = entryIt.next(); + method.addParameter(entry.getKey(), entry.getValue()); + } + Date now = new Date(); + String ts = Long.toString(now.getTime() / 1000); + String hash = DigestUtils.shaHex(this.sharedSecret + ts).toLowerCase(); + method.addParameter("ts", ts); + method.addParameter("hash", hash); + logger.debug("Sending POST message to " + method.getURI().getURI() + " with parameters " + + Arrays.toString(method.getParameters())); + int statusCode = httpClient.executeMethod(method); + if (statusCode != HttpStatus.SC_OK) + { + logger.debug("Server replied with a " + statusCode + " HTTP status code (" + + HttpStatus.getStatusText(statusCode) + ")"); + throw new SlideShareErrorException(statusCode, HttpStatus.getStatusText(statusCode)); + } + if (logger.isDebugEnabled()) + { + logger.debug(method.getResponseBodyAsString()); + } + InputStream result = new ByteArrayInputStream(method.getResponseBody()); + method.releaseConnection(); + return result; + } + + public InputStream sendMultiPartMessage(String url, Map parameters, Map files) + throws IOException, SlideShareErrorException + { + PostMethod method = new PostMethod(url); + List partList = new ArrayList(); + partList.add(createStringPart("api_key", this.apiKey)); + Date now = new Date(); + String ts = Long.toString(now.getTime() / 1000); + String hash = DigestUtils.shaHex(this.sharedSecret + ts).toLowerCase(); + partList.add(createStringPart("ts", ts)); + partList.add(createStringPart("hash", hash)); + Iterator> entryIt = parameters.entrySet().iterator(); + while (entryIt.hasNext()) + { + Map.Entry entry = entryIt.next(); + partList.add(createStringPart(entry.getKey(), entry.getValue())); + } + Iterator> entryFileIt = files.entrySet().iterator(); + while (entryFileIt.hasNext()) + { + Map.Entry entry = entryFileIt.next(); + partList.add(createFilePart(entry.getKey(), entry.getValue())); + } + MultipartRequestEntity requestEntity = new MultipartRequestEntity(partList.toArray(new Part[partList.size()]), + method.getParams()); + method.setRequestEntity(requestEntity); + logger.debug("Sending multipart POST message to " + method.getURI().getURI() + " with parts " + partList); + int statusCode = httpClient.executeMethod(method); + if (statusCode != HttpStatus.SC_OK) + { + logger.debug("Server replied with a " + statusCode + " HTTP status code (" + + HttpStatus.getStatusText(statusCode) + ")"); + throw new SlideShareErrorException(statusCode, HttpStatus.getStatusText(statusCode)); + } + if (logger.isDebugEnabled()) + { + logger.debug(method.getResponseBodyAsString()); + } + InputStream result = new ByteArrayInputStream(method.getResponseBody()); + method.releaseConnection(); + return result; + } + + public InputStream sendGetMessage(String url, Map parameters) throws IOException, + SlideShareErrorException + { + GetMethod method = new GetMethod(url); + NameValuePair[] params = new NameValuePair[parameters.size() + 3]; + int i = 0; + params[i++] = new NameValuePair("api_key", this.apiKey); + Iterator> entryIt = parameters.entrySet().iterator(); + while (entryIt.hasNext()) + { + Map.Entry entry = entryIt.next(); + params[i++] = new NameValuePair(entry.getKey(), entry.getValue()); + } + Date now = new Date(); + String ts = Long.toString(now.getTime() / 1000); + String hash = DigestUtils.shaHex(this.sharedSecret + ts).toLowerCase(); + params[i++] = new NameValuePair("ts", ts); + params[i++] = new NameValuePair("hash", hash); + method.setQueryString(params); + logger.debug("Sending GET message to " + method.getURI().getURI() + " with parameters " + + Arrays.toString(params)); + int statusCode = httpClient.executeMethod(method); + if (statusCode != HttpStatus.SC_OK) + { + logger.debug("Server replied with a " + statusCode + " HTTP status code (" + + HttpStatus.getStatusText(statusCode) + ")"); + throw new SlideShareErrorException(statusCode, HttpStatus.getStatusText(statusCode)); + } + if (logger.isDebugEnabled()) + { + logger.debug(method.getResponseBodyAsString()); + } + InputStream result = new ByteArrayInputStream(method.getResponseBody()); + method.releaseConnection(); + return result; + } + + private StringPart createStringPart(String name, String value) + { + StringPart stringPart = new StringPart(name, value); + stringPart.setContentType(null); + stringPart.setTransferEncoding(null); + stringPart.setCharSet("UTF-8"); + return stringPart; + } + + private FilePart createFilePart(String name, File value) throws FileNotFoundException + { + FilePart filePart = new FilePart(name, value); + filePart.setTransferEncoding(null); + filePart.setCharSet(null); + return filePart; + } + +} diff --git a/source/java/org/alfresco/repo/publishing/slideshare/SlideSharePublishAction.java b/source/java/org/alfresco/repo/publishing/slideshare/SlideSharePublishAction.java deleted file mode 100644 index 70f239572f..0000000000 --- a/source/java/org/alfresco/repo/publishing/slideshare/SlideSharePublishAction.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * 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.slideshare; - -import java.io.File; -import java.util.List; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; -import org.alfresco.repo.publishing.PublishingModel; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.tagging.TaggingService; -import org.alfresco.util.Pair; -import org.alfresco.util.TempFileProvider; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import com.benfante.jslideshare.SlideShareAPI; -import com.benfante.jslideshare.messages.SlideshowInfo; - -public class SlideSharePublishAction extends ActionExecuterAbstractBase -{ - private final static Log log = LogFactory.getLog(SlideSharePublishAction.class); - private final static int STATUS_QUEUED = 0; - // private final static int STATUS_CONVERTING = 1; - private final static int STATUS_SUCCEEDED = 2; - private final static int STATUS_FAILED = 3; - private final static int STATUS_TIMED_OUT = 10; - public static final String NAME = "publish_slideshare"; - private static final String ERROR_SLIDESHARE_CONVERSION_FAILED = "publish.slideshare.conversionFailed"; - private static final String ERROR_SLIDESHARE_CONVERSION_TIMED_OUT = "publish.slideshare.conversionTimedOut"; - - private NodeService nodeService; - private ContentService contentService; - private TaggingService taggingService; - private SlideSharePublishingHelper slideShareHelper; - - private long timeoutMilliseconds = 40L * 60L * 1000L; // 40 mins default - - public void setSlideShareHelper(SlideSharePublishingHelper slideShareHelper) - { - this.slideShareHelper = slideShareHelper; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setContentService(ContentService contentService) - { - this.contentService = contentService; - } - - public void setTaggingService(TaggingService taggingService) - { - this.taggingService = taggingService; - } - - public void setTimeoutMilliseconds(long timeoutMilliseconds) - { - this.timeoutMilliseconds = timeoutMilliseconds; - } - - @Override - protected void executeImpl(Action action, NodeRef nodeRef) - { - Pair usernamePassword = slideShareHelper.getSlideShareCredentialsForNode(nodeRef); - if (usernamePassword == null) - { - throw new AlfrescoRuntimeException("publish.failed.no_credentials_found"); - } - SlideShareAPI api = slideShareHelper - .getSlideShareApi(usernamePassword.getFirst(), usernamePassword.getSecond()); - - ContentReader reader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); - if (reader.exists()) - { - File contentFile; - String mime = reader.getMimetype(); - - String extension = slideShareHelper.getAllowedMimeTypes().get(mime); - if (extension == null) - extension = ""; - - boolean deleteContentFileOnCompletion = false; - - // SlideShare seems to work entirely off file extension, so we - // always copy onto the - // file system and upload from there. - File tempDir = TempFileProvider.getLongLifeTempDir("slideshare"); - contentFile = TempFileProvider.createTempFile("slideshare", extension, tempDir); - reader.getContent(contentFile); - deleteContentFileOnCompletion = true; - - try - { - - String name = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); - String title = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE); - if (title == null || title.length() == 0) - { - title = name; - } - String description = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_DESCRIPTION); - if (description == null || description.length() == 0) - { - description = title; - } - - List tagList = taggingService.getTags(nodeRef); - StringBuilder tags = new StringBuilder(); - for (String tag : tagList) - { - tags.append(tag); - tags.append(' '); - } - - String assetId = api.uploadSlideshow(usernamePassword.getFirst(), usernamePassword.getSecond(), title, - contentFile, description, tags.toString(), false, false, false, false, false); - - String url = null; - int status = STATUS_QUEUED; - boolean finished = false; - long timeoutTime = System.currentTimeMillis() + timeoutMilliseconds; - // Fetch the slideshow info every 5 seconds for 5 minutes... - while (!finished) - { - SlideshowInfo slideInfo = api.getSlideshowInfo(assetId, ""); - if (slideInfo != null) - { - if (url == null) - { - url = slideInfo.getUrl(); - if (log.isInfoEnabled()) - { - log.info("SlideShare has provided a URL for asset " + assetId + ": " + url); - } - } - status = slideInfo.getStatus(); - } - finished = (status == STATUS_FAILED || status == STATUS_SUCCEEDED); - - if (!finished) - { - if (System.currentTimeMillis() < timeoutTime) - { - try - { - Thread.sleep(30000L); - } - catch (InterruptedException e) - { - } - } - else - { - status = STATUS_TIMED_OUT; - finished = true; - } - } - } - if (status == STATUS_SUCCEEDED) - { - if (log.isInfoEnabled()) - { - log.info("File " + name + " has been published to SlideShare with id " + assetId + " at URL " - + url); - } - nodeService.addAspect(nodeRef, SlideSharePublishingModel.ASPECT_ASSET, null); - nodeService.setProperty(nodeRef, PublishingModel.PROP_ASSET_ID, assetId); - nodeService.setProperty(nodeRef, PublishingModel.PROP_ASSET_URL, url); - } - else - { - throw new AlfrescoRuntimeException(status == STATUS_FAILED ? ERROR_SLIDESHARE_CONVERSION_FAILED - : ERROR_SLIDESHARE_CONVERSION_TIMED_OUT); - } - } - finally - { - if (deleteContentFileOnCompletion) - { - contentFile.delete(); - } - } - } - } - - @Override - protected void addParameterDefinitions(List paramList) - { - } -} diff --git a/source/java/org/alfresco/repo/publishing/slideshare/SlideSharePublishingHelper.java b/source/java/org/alfresco/repo/publishing/slideshare/SlideSharePublishingHelper.java index e4a24b2b4b..a098b0201a 100644 --- a/source/java/org/alfresco/repo/publishing/slideshare/SlideSharePublishingHelper.java +++ b/source/java/org/alfresco/repo/publishing/slideshare/SlideSharePublishingHelper.java @@ -18,6 +18,7 @@ */ package org.alfresco.repo.publishing.slideshare; +import java.io.Serializable; import java.util.Collections; import java.util.Map; import java.util.TreeMap; @@ -25,8 +26,7 @@ import java.util.TreeMap; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.node.encryption.MetadataEncryptor; import org.alfresco.repo.publishing.PublishingModel; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; import org.alfresco.util.Pair; import com.benfante.jslideshare.SlideShareAPI; @@ -52,15 +52,9 @@ public class SlideSharePublishingHelper } private Map allowedMimeTypes = Collections.unmodifiableMap(DEFAULT_MIME_TYPES); - private NodeService nodeService; private SlideShareConnector slideshareConnector; private MetadataEncryptor encryptor; - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - public void setSlideshareConnector(SlideShareConnector slideshareConnector) { this.slideshareConnector = slideshareConnector; @@ -91,23 +85,16 @@ public class SlideSharePublishingHelper return new SlideShareApiImpl(slideshareConnector); } - public Pair getSlideShareCredentialsForNode(NodeRef publishNode) + public Pair getSlideShareCredentialsFromChannelProperties(Map channelProperties) { Pair result = null; - if (nodeService.exists(publishNode)) + String username = (String) encryptor.decrypt(PublishingModel.PROP_CHANNEL_USERNAME, + channelProperties.get(PublishingModel.PROP_CHANNEL_USERNAME)); + String password = (String) encryptor.decrypt(PublishingModel.PROP_CHANNEL_PASSWORD, + channelProperties.get(PublishingModel.PROP_CHANNEL_PASSWORD)); + if (username != null && password != null) { - NodeRef parent = nodeService.getPrimaryParent(publishNode).getParentRef(); - if (nodeService.hasAspect(parent, SlideSharePublishingModel.ASPECT_DELIVERY_CHANNEL)) - { - String username = (String) encryptor.decrypt(PublishingModel.PROP_CHANNEL_USERNAME, nodeService - .getProperty(parent, PublishingModel.PROP_CHANNEL_USERNAME)); - String password = (String) encryptor.decrypt(PublishingModel.PROP_CHANNEL_PASSWORD, nodeService - .getProperty(parent, PublishingModel.PROP_CHANNEL_PASSWORD)); - if (username != null && password != null) - { - result = new Pair(username, password); - } - } + result = new Pair(username, password); } return result; } diff --git a/source/java/org/alfresco/repo/publishing/slideshare/SlideShareTest.java b/source/java/org/alfresco/repo/publishing/slideshare/SlideShareTest.java index 0bc043fd42..8109f6632c 100644 --- a/source/java/org/alfresco/repo/publishing/slideshare/SlideShareTest.java +++ b/source/java/org/alfresco/repo/publishing/slideshare/SlideShareTest.java @@ -36,8 +36,6 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.publishing.channels.Channel; import org.alfresco.service.cmr.publishing.channels.ChannelService; @@ -115,6 +113,8 @@ public class SlideShareTest extends BaseSpringTest // text "YOUR_USER_NAME" and "YOUR_PASSWORD" appear. public void xtestSlideSharePublishAndUnpublishActions() throws Exception { + final SlideShareChannelType channelType = (SlideShareChannelType)channelService.getChannelType(SlideShareChannelType.ID); + final String channelName = GUID.generate(); final List nodes = transactionHelper.doInTransaction(new RetryingTransactionCallback>() { public List execute() throws Throwable @@ -127,7 +127,7 @@ public class SlideShareTest extends BaseSpringTest // "YOUR_PASSWORD"); props.put(PublishingModel.PROP_CHANNEL_USERNAME, "YOUR_USER_NAME"); props.put(PublishingModel.PROP_CHANNEL_PASSWORD, "YOUR_PASSWORD"); - Channel channel = channelService.createChannel(SlideShareChannelType.ID, GUID.generate(), props); + Channel channel = channelService.createChannel(SlideShareChannelType.ID, channelName, props); NodeRef channelNode = channel.getNodeRef(); @@ -162,29 +162,31 @@ public class SlideShareTest extends BaseSpringTest { public NodeRef execute() throws Throwable { + Map channelProperties = channelService.getChannelByName(channelName).getProperties(); for (NodeRef node : nodes) { - ActionService actionService = serviceRegistry.getActionService(); - Action publishAction = actionService.createAction(SlideSharePublishAction.NAME); - actionService.executeAction(publishAction, node); + channelType.publish(node, channelProperties); Map props = nodeService.getProperties(node); Assert.assertTrue(nodeService.hasAspect(node, SlideSharePublishingModel.ASPECT_ASSET)); Assert.assertNotNull(props.get(PublishingModel.PROP_ASSET_ID)); Assert.assertNotNull(props.get(PublishingModel.PROP_ASSET_URL)); - System.out.println("Test file: " + testNodeMap.get(node)); + System.out.println("Published test file: " + testNodeMap.get(node)); System.out.println("SlideShare id: " + props.get(PublishingModel.PROP_ASSET_ID)); System.out.println("SlideShare URL: " + props.get(PublishingModel.PROP_ASSET_URL)); } - // Action unpublishAction = - // actionService.createAction(SlideShareUnpublishAction.NAME); - // actionService.executeAction(unpublishAction, node); - // props = nodeService.getProperties(node); - // Assert.assertFalse(nodeService.hasAspect(node, - // SlideSharePublishingModel.ASPECT_ASSET)); - // Assert.assertNull(props.get(SlideSharePublishingModel.PROP_ASSET_ID)); - // Assert.assertNull(props.get(SlideSharePublishingModel.PROP_ASSET_URL)); + for (NodeRef node : nodes) + { + Map props = nodeService.getProperties(node); + channelType.unpublish(node, channelProperties); + props = nodeService.getProperties(node); + Assert.assertFalse(nodeService.hasAspect(node, SlideSharePublishingModel.ASPECT_ASSET)); + Assert.assertNull(props.get(PublishingModel.PROP_ASSET_ID)); + Assert.assertNull(props.get(PublishingModel.PROP_ASSET_URL)); + + System.out.println("Unpublished test file: " + testNodeMap.get(node)); + } return null; } }); diff --git a/source/java/org/alfresco/repo/publishing/slideshare/SlideShareUnpublishAction.java b/source/java/org/alfresco/repo/publishing/slideshare/SlideShareUnpublishAction.java deleted file mode 100644 index ee3569d18f..0000000000 --- a/source/java/org/alfresco/repo/publishing/slideshare/SlideShareUnpublishAction.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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.slideshare; - -import java.util.List; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; -import org.alfresco.repo.publishing.PublishingModel; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.util.Pair; - -public class SlideShareUnpublishAction extends ActionExecuterAbstractBase -{ - public static final String NAME = "unpublish_slideshare"; - - private NodeService nodeService; - private SlideSharePublishingHelper slideShareHelper; - - public void setSlideShareHelper(SlideSharePublishingHelper slideShareHelper) - { - this.slideShareHelper = slideShareHelper; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - @Override - protected void executeImpl(Action action, NodeRef nodeRef) - { - if (nodeService.hasAspect(nodeRef, SlideSharePublishingModel.ASPECT_ASSET)) - { - String assetId = (String) nodeService.getProperty(nodeRef, PublishingModel.PROP_ASSET_ID); - if (assetId != null) - { - Pair usernamePassword = slideShareHelper.getSlideShareCredentialsForNode(nodeRef); - if (usernamePassword == null) - { - throw new AlfrescoRuntimeException("publish.failed.no_credentials_found"); - } - SlideShareApi api = slideShareHelper.getSlideShareApi( - usernamePassword.getFirst(), usernamePassword.getSecond()); - - api.deleteSlideshow(usernamePassword.getFirst(), usernamePassword.getSecond(), assetId); - nodeService.removeAspect(nodeRef, SlideSharePublishingModel.ASPECT_ASSET); - nodeService.removeAspect(nodeRef, PublishingModel.ASPECT_ASSET); - } - } - } - - @Override - protected void addParameterDefinitions(List paramList) - { - } -} diff --git a/source/java/org/alfresco/repo/publishing/test/TestChannelType1.java b/source/java/org/alfresco/repo/publishing/test/TestChannelType1.java index fd6e1804e7..1a3f2532c3 100644 --- a/source/java/org/alfresco/repo/publishing/test/TestChannelType1.java +++ b/source/java/org/alfresco/repo/publishing/test/TestChannelType1.java @@ -99,7 +99,7 @@ public class TestChannelType1 extends AbstractChannelType } @Override - public void updateStatus(Channel channel, String status, Map properties) + public void sendStatusUpdate(Channel channel, String status) { //Deliberately blank } diff --git a/source/java/org/alfresco/repo/publishing/test/TestChannelType2.java b/source/java/org/alfresco/repo/publishing/test/TestChannelType2.java index 92941d59ed..1e08fe98ba 100644 --- a/source/java/org/alfresco/repo/publishing/test/TestChannelType2.java +++ b/source/java/org/alfresco/repo/publishing/test/TestChannelType2.java @@ -99,7 +99,7 @@ public class TestChannelType2 extends AbstractChannelType } @Override - public void updateStatus(Channel channel, String status, Map properties) + public void sendStatusUpdate(Channel channel, String status) { //Deliberately blank } diff --git a/source/java/org/alfresco/repo/publishing/test/TestChannelType3.java b/source/java/org/alfresco/repo/publishing/test/TestChannelType3.java index 92099149de..0a86c44fae 100644 --- a/source/java/org/alfresco/repo/publishing/test/TestChannelType3.java +++ b/source/java/org/alfresco/repo/publishing/test/TestChannelType3.java @@ -99,7 +99,7 @@ public class TestChannelType3 extends AbstractChannelType } @Override - public void updateStatus(Channel channel, String status, Map properties) + public void sendStatusUpdate(Channel channel, String status) { //Deliberately blank } diff --git a/source/java/org/alfresco/repo/publishing/twitter/TwitterChannelType.java b/source/java/org/alfresco/repo/publishing/twitter/TwitterChannelType.java index d601c9aa5c..2afa24a9a6 100644 --- a/source/java/org/alfresco/repo/publishing/twitter/TwitterChannelType.java +++ b/source/java/org/alfresco/repo/publishing/twitter/TwitterChannelType.java @@ -18,11 +18,6 @@ */ package org.alfresco.repo.publishing.twitter; -import java.io.Serializable; -import java.util.Collections; -import java.util.Map; -import java.util.Set; - import org.alfresco.repo.publishing.AbstractOAuth1ChannelType; import org.alfresco.service.cmr.publishing.channels.Channel; import org.alfresco.service.cmr.repository.NodeRef; @@ -67,30 +62,6 @@ public class TwitterChannelType extends AbstractOAuth1ChannelType return ID; } - @Override - public Set getSupportedContentTypes() - { - return Collections.emptySet(); - } - - @Override - public Set getSupportedMimeTypes() - { - return Collections.emptySet(); - } - - @Override - public void publish(NodeRef nodeToPublish, Map properties) - { - //NO-OP - } - - @Override - public void unpublish(NodeRef nodeToUnpublish, Map properties) - { - //NO-OP - } - @Override public int getMaximumStatusLength() { @@ -98,7 +69,7 @@ public class TwitterChannelType extends AbstractOAuth1ChannelType } @Override - public void updateStatus(Channel channel, String status, Map properties) + public void sendStatusUpdate(Channel channel, String status) { Connection connection = getConnectionForChannel(channel.getNodeRef()); if (log.isInfoEnabled()) @@ -111,7 +82,7 @@ public class TwitterChannelType extends AbstractOAuth1ChannelType @Override public String getNodeUrl(NodeRef node) { - throw new UnsupportedOperationException(); + return null; } } diff --git a/source/java/org/alfresco/repo/publishing/youtube/YouTubeChannelType.java b/source/java/org/alfresco/repo/publishing/youtube/YouTubeChannelType.java index 7fd89325e4..44338189e0 100644 --- a/source/java/org/alfresco/repo/publishing/youtube/YouTubeChannelType.java +++ b/source/java/org/alfresco/repo/publishing/youtube/YouTubeChannelType.java @@ -18,49 +18,84 @@ */ package org.alfresco.repo.publishing.youtube; +import java.io.File; +import java.io.IOException; import java.io.Serializable; +import java.net.MalformedURLException; +import java.net.URL; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.filestore.FileContentReader; import org.alfresco.repo.publishing.AbstractChannelType; import org.alfresco.repo.publishing.PublishingModel; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ActionService; -import org.alfresco.service.cmr.publishing.channels.Channel; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.tagging.TaggingService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.TempFileProvider; import org.alfresco.util.collections.CollectionUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.google.gdata.client.media.ResumableGDataFileUploader; +import com.google.gdata.client.uploader.ProgressListener; +import com.google.gdata.client.uploader.ResumableHttpFileUploader; +import com.google.gdata.client.youtube.YouTubeService; +import com.google.gdata.data.media.MediaFileSource; +import com.google.gdata.data.media.mediarss.MediaCategory; +import com.google.gdata.data.media.mediarss.MediaDescription; +import com.google.gdata.data.media.mediarss.MediaKeywords; +import com.google.gdata.data.media.mediarss.MediaTitle; +import com.google.gdata.data.youtube.VideoEntry; +import com.google.gdata.data.youtube.YouTubeMediaGroup; +import com.google.gdata.data.youtube.YouTubeNamespace; +import com.google.gdata.util.ServiceException; public class YouTubeChannelType extends AbstractChannelType { + private final static Log log = LogFactory.getLog(YouTubeChannelType.class); private final static Set DEFAULT_SUPPORTED_MIME_TYPES = CollectionUtils.unmodifiableSet( - MimetypeMap.MIMETYPE_VIDEO_MPG, - MimetypeMap.MIMETYPE_VIDEO_MP4, - MimetypeMap.MIMETYPE_VIDEO_FLV, - MimetypeMap.MIMETYPE_VIDEO_3GP, - MimetypeMap.MIMETYPE_VIDEO_AVI, - MimetypeMap.MIMETYPE_VIDEO_QUICKTIME, - MimetypeMap.MIMETYPE_VIDEO_WMV - ); - + MimetypeMap.MIMETYPE_VIDEO_MPG, MimetypeMap.MIMETYPE_VIDEO_MP4, MimetypeMap.MIMETYPE_VIDEO_FLV, + MimetypeMap.MIMETYPE_VIDEO_3GP, MimetypeMap.MIMETYPE_VIDEO_AVI, MimetypeMap.MIMETYPE_VIDEO_QUICKTIME, + MimetypeMap.MIMETYPE_VIDEO_WMV); + + public static final String RESUMABLE_UPLOAD_URL = "http://uploads.gdata.youtube.com/resumable/feeds/api/users/default/uploads"; + + /** Time interval at which upload task will notify about the progress */ + private static final int PROGRESS_UPDATE_INTERVAL = 1000; + + /** Max size for each upload chunk */ + private static final int DEFAULT_CHUNK_SIZE = 10000000; + private Set supportedMimeTypes = DEFAULT_SUPPORTED_MIME_TYPES; public final static String ID = "youtube"; - private NodeService nodeService; - private ActionService actionService; - - public void setNodeService(NodeService nodeService) + private YouTubePublishingHelper youTubeHelper; + private ContentService contentService; + private TaggingService taggingService; + + public void setYouTubeHelper(YouTubePublishingHelper youTubeHelper) { - this.nodeService = nodeService; + this.youTubeHelper = youTubeHelper; } - public void setActionService(ActionService actionService) + public void setContentService(ContentService contentService) { - this.actionService = actionService; + this.contentService = contentService; + } + + public void setTaggingService(TaggingService taggingService) + { + this.taggingService = taggingService; } public void setSupportedMimeTypes(Set supportedMimeTypes) @@ -98,12 +133,6 @@ public class YouTubeChannelType extends AbstractChannelType return ID; } - @Override - public Set getSupportedContentTypes() - { - return Collections.emptySet(); - } - @Override public Set getSupportedMimeTypes() { @@ -113,31 +142,182 @@ public class YouTubeChannelType extends AbstractChannelType @Override public void publish(NodeRef nodeToPublish, Map properties) { - Action youtubePublishAction = actionService.createAction(YouTubePublishAction.NAME); - actionService.executeAction(youtubePublishAction, nodeToPublish); + YouTubeService service = youTubeHelper.getYouTubeServiceFromChannelProperties(properties); + if (service != null) + { + try + { + uploadVideo(service, nodeToPublish); + } + catch (Exception ex) + { + log.error("Failed to send asset to YouTube", ex); + throw new AlfrescoRuntimeException("exception.publishing.youtube.publishFailed", ex); + } + } } @Override public void unpublish(NodeRef nodeToUnpublish, Map properties) { - Action youtubeUnpublishAction = actionService.createAction(YouTubeUnpublishAction.NAME); - actionService.executeAction(youtubeUnpublishAction, nodeToUnpublish); - } - - @Override - public void updateStatus(Channel channel, String status, Map properties) - { - throw new UnsupportedOperationException(); - } - - @Override - public String getNodeUrl(NodeRef node) - { - String url = null; - if (node != null && nodeService.exists(node) && nodeService.hasAspect(node, PublishingModel.ASPECT_ASSET)) + YouTubeService service = youTubeHelper.getYouTubeServiceFromChannelProperties(properties); + if (service != null) { - url = (String)nodeService.getProperty(node, PublishingModel.PROP_ASSET_URL); + try + { + removeVideo(service, nodeToUnpublish); + } + catch (Exception ex) + { + log.error("Failed to remove asset from YouTube", ex); + throw new AlfrescoRuntimeException("exception.publishing.youtube.unpublishFailed", ex); + } } - return url; } -} + + private void removeVideo(YouTubeService service, NodeRef nodeRef) throws MalformedURLException, IOException, + ServiceException + { + NodeService nodeService = getNodeService(); + if (nodeService.hasAspect(nodeRef, YouTubePublishingModel.ASPECT_ASSET)) + { + String youtubeId = (String) nodeService.getProperty(nodeRef, PublishingModel.PROP_ASSET_ID); + if (youtubeId != null) + { + String videoEntryUrl = "https://gdata.youtube.com/feeds/api/users/default/uploads/" + youtubeId; + VideoEntry videoEntry = service.getEntry(new URL(videoEntryUrl), VideoEntry.class); + videoEntry.delete(); + nodeService.removeAspect(nodeRef, YouTubePublishingModel.ASPECT_ASSET); + nodeService.removeAspect(nodeRef, PublishingModel.ASPECT_ASSET); + } + } + } + + private void uploadVideo(YouTubeService service, NodeRef nodeRef) throws IOException, ServiceException, + InterruptedException + { + NodeService nodeService = getNodeService(); + ContentReader reader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); + if (reader.exists()) + { + File contentFile; + boolean deleteContentFileOnCompletion = false; + if (FileContentReader.class.isAssignableFrom(reader.getClass())) + { + // Grab the content straight from the content store if we can... + contentFile = ((FileContentReader) reader).getFile(); + } + else + { + // ...otherwise copy it to a temp file and use the copy... + File tempDir = TempFileProvider.getLongLifeTempDir("youtube"); + contentFile = TempFileProvider.createTempFile("youtube", "", tempDir); + reader.getContent(contentFile); + deleteContentFileOnCompletion = true; + } + MediaFileSource ms = new MediaFileSource(contentFile, reader.getMimetype()); + + String videoName = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + String videoTitle = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE); + if (videoTitle == null || videoTitle.length() == 0) + { + videoTitle = videoName; + } + String videoDescription = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_DESCRIPTION); + if (videoDescription == null || videoDescription.length() == 0) + { + videoDescription = videoTitle; + } + + VideoEntry newEntry = new VideoEntry(); + YouTubeMediaGroup mg = newEntry.getOrCreateMediaGroup(); + mg.addCategory(new MediaCategory(YouTubeNamespace.CATEGORY_SCHEME, "Tech")); + mg.setTitle(new MediaTitle()); + mg.getTitle().setPlainTextContent(videoTitle); + mg.setKeywords(new MediaKeywords()); + List tags = taggingService.getTags(nodeRef); + for (String tag : tags) + { + mg.getKeywords().addKeyword(tag); + } + mg.setDescription(new MediaDescription()); + mg.getDescription().setPlainTextContent(videoDescription); + + FileUploadProgressListener listener = new FileUploadProgressListener(videoName); + ResumableGDataFileUploader uploader = new ResumableGDataFileUploader.Builder(service, new URL( + RESUMABLE_UPLOAD_URL), ms, newEntry).title(videoTitle).trackProgress(listener, + PROGRESS_UPDATE_INTERVAL).chunkSize(DEFAULT_CHUNK_SIZE).build(); + + uploader.start(); + while (!uploader.isDone()) + { + Thread.sleep(PROGRESS_UPDATE_INTERVAL); + } + + switch (uploader.getUploadState()) + { + case COMPLETE: + VideoEntry entry = uploader.getResponse(VideoEntry.class); + String videoId = entry.getMediaGroup().getVideoId(); + String contentUrl = entry.getMediaGroup().getContents().get(0).getUrl(); + String playerUrl = entry.getMediaGroup().getPlayer().getUrl(); + if (log.isDebugEnabled()) + { + log.debug("Video content uploaded successfully: " + videoName); + log.debug("YouTube video id is " + videoId); + log.debug("YouTube content URL is " + contentUrl); + log.debug("YouTube video player URL is " + playerUrl); + } + nodeService.addAspect(nodeRef, YouTubePublishingModel.ASPECT_ASSET, null); + nodeService.setProperty(nodeRef, PublishingModel.PROP_ASSET_ID, videoId); + nodeService.setProperty(nodeRef, PublishingModel.PROP_ASSET_URL, playerUrl); + break; + case CLIENT_ERROR: + log.error("Video content failed to upload: " + videoName); + break; + default: + log.warn("Unknown upload state. Video content may not have uploaded: " + videoName + "(" + + uploader.getUploadState() + ") :" + nodeRef); + break; + } + + if (deleteContentFileOnCompletion) + { + contentFile.delete(); + } + } + } + + /** + * A {@link ProgressListener} implementation to track upload progress. The + * listener can track multiple uploads at the same time. + */ + private class FileUploadProgressListener implements ProgressListener + { + String videoName; + + public FileUploadProgressListener(String videoName) + { + this.videoName = videoName; + } + + public synchronized void progressChanged(ResumableHttpFileUploader uploader) + { + switch (uploader.getUploadState()) + { + case COMPLETE: + log.info("Upload Completed: " + videoName); + break; + case CLIENT_ERROR: + log.error("Upload Failed: " + videoName); + break; + case IN_PROGRESS: + log.info(videoName + String.format(" %3.0f", uploader.getProgress() * 100) + "%"); + break; + case NOT_STARTED: + log.info("Upload Not Started: " + videoName); + break; + } + } + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/publishing/youtube/YouTubePublishAction.java b/source/java/org/alfresco/repo/publishing/youtube/YouTubePublishAction.java deleted file mode 100644 index c8593be0f0..0000000000 --- a/source/java/org/alfresco/repo/publishing/youtube/YouTubePublishAction.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * 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.youtube; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.util.List; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; -import org.alfresco.repo.content.filestore.FileContentReader; -import org.alfresco.repo.publishing.PublishingModel; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.tagging.TaggingService; -import org.alfresco.util.TempFileProvider; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import com.google.gdata.client.media.ResumableGDataFileUploader; -import com.google.gdata.client.uploader.ProgressListener; -import com.google.gdata.client.uploader.ResumableHttpFileUploader; -import com.google.gdata.client.youtube.YouTubeService; -import com.google.gdata.data.media.MediaFileSource; -import com.google.gdata.data.media.mediarss.MediaCategory; -import com.google.gdata.data.media.mediarss.MediaDescription; -import com.google.gdata.data.media.mediarss.MediaKeywords; -import com.google.gdata.data.media.mediarss.MediaTitle; -import com.google.gdata.data.youtube.VideoEntry; -import com.google.gdata.data.youtube.YouTubeMediaGroup; -import com.google.gdata.data.youtube.YouTubeNamespace; -import com.google.gdata.util.ServiceException; - -public class YouTubePublishAction extends ActionExecuterAbstractBase -{ - private final static Log log = LogFactory.getLog(YouTubePublishAction.class); - - public static final String NAME = "publish_youtube"; - - public static final String RESUMABLE_UPLOAD_URL = "http://uploads.gdata.youtube.com/resumable/feeds/api/users/default/uploads"; - - /** Time interval at which upload task will notify about the progress */ - private static final int PROGRESS_UPDATE_INTERVAL = 1000; - - /** Max size for each upload chunk */ - private static final int DEFAULT_CHUNK_SIZE = 10000000; - - private NodeService nodeService; - private ContentService contentService; - private TaggingService taggingService; - private YouTubePublishingHelper youTubeHelper; - - public void setYouTubeHelper(YouTubePublishingHelper youTubeHelper) - { - this.youTubeHelper = youTubeHelper; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setContentService(ContentService contentService) - { - this.contentService = contentService; - } - - public void setTaggingService(TaggingService taggingService) - { - this.taggingService = taggingService; - } - - @Override - protected void executeImpl(Action action, NodeRef actionedUponNodeRef) - { - YouTubeService service = youTubeHelper.getYouTubeServiceForNode(actionedUponNodeRef); - if (service != null) - { - try - { - uploadVideo(service, actionedUponNodeRef); - } - catch(Exception ex) - { - log.error("Failed to send asset to YouTube", ex); - throw new AlfrescoRuntimeException("exception.publishing.youtube.publishFailed", ex); - } - } - } - - private void uploadVideo(YouTubeService service, NodeRef nodeRef) throws IOException, ServiceException, - InterruptedException - { - ContentReader reader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); - if (reader.exists()) - { - File contentFile; - boolean deleteContentFileOnCompletion = false; - if (FileContentReader.class.isAssignableFrom(reader.getClass())) - { - //Grab the content straight from the content store if we can... - contentFile = ((FileContentReader)reader).getFile(); - } - else - { - //...otherwise copy it to a temp file and use the copy... - File tempDir = TempFileProvider.getLongLifeTempDir("youtube"); - contentFile = TempFileProvider.createTempFile("youtube", "", tempDir); - reader.getContent(contentFile); - deleteContentFileOnCompletion = true; - } - MediaFileSource ms = new MediaFileSource(contentFile, reader.getMimetype()); - - String videoName = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); - String videoTitle = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE); - if (videoTitle == null || videoTitle.length() == 0) - { - videoTitle = videoName; - } - String videoDescription = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_DESCRIPTION); - if (videoDescription == null || videoDescription.length() == 0) - { - videoDescription = videoTitle; - } - - VideoEntry newEntry = new VideoEntry(); - YouTubeMediaGroup mg = newEntry.getOrCreateMediaGroup(); - mg.addCategory(new MediaCategory(YouTubeNamespace.CATEGORY_SCHEME, "Tech")); - mg.setTitle(new MediaTitle()); - mg.getTitle().setPlainTextContent(videoTitle); - mg.setKeywords(new MediaKeywords()); - List tags = taggingService.getTags(nodeRef); - for (String tag : tags) - { - mg.getKeywords().addKeyword(tag); - } - mg.setDescription(new MediaDescription()); - mg.getDescription().setPlainTextContent(videoDescription); - - FileUploadProgressListener listener = new FileUploadProgressListener(videoName); - ResumableGDataFileUploader uploader = new ResumableGDataFileUploader.Builder(service, new URL( - RESUMABLE_UPLOAD_URL), ms, newEntry).title(videoTitle).trackProgress(listener, - PROGRESS_UPDATE_INTERVAL).chunkSize(DEFAULT_CHUNK_SIZE).build(); - - uploader.start(); - while (!uploader.isDone()) - { - Thread.sleep(PROGRESS_UPDATE_INTERVAL); - } - - switch (uploader.getUploadState()) - { - case COMPLETE: - VideoEntry entry = uploader.getResponse(VideoEntry.class); - String videoId = entry.getMediaGroup().getVideoId(); - String contentUrl = entry.getMediaGroup().getContents().get(0).getUrl(); - String playerUrl = entry.getMediaGroup().getPlayer().getUrl(); - if (log.isDebugEnabled()) - { - log.debug("Video content uploaded successfully: " + videoName); - log.debug("YouTube video id is " + videoId); - log.debug("YouTube content URL is " + contentUrl); - log.debug("YouTube video player URL is " + playerUrl); - } - nodeService.addAspect(nodeRef, YouTubePublishingModel.ASPECT_ASSET, null); - nodeService.setProperty(nodeRef, PublishingModel.PROP_ASSET_ID, videoId); - nodeService.setProperty(nodeRef, PublishingModel.PROP_ASSET_URL, playerUrl); - break; - case CLIENT_ERROR: - log.error("Video content failed to upload: " + videoName); - break; - default: - log.warn("Unknown upload state. Video content may not have uploaded: " + videoName + "(" - + uploader.getUploadState() + ") :" + nodeRef); - break; - } - - if (deleteContentFileOnCompletion) - { - contentFile.delete(); - } - } - } - - @Override - protected void addParameterDefinitions(List paramList) - { - } - - /** - * A {@link ProgressListener} implementation to track upload progress. The - * listener can track multiple uploads at the same time. - */ - private class FileUploadProgressListener implements ProgressListener - { - String videoName; - - public FileUploadProgressListener(String videoName) - { - this.videoName = videoName; - } - - public synchronized void progressChanged(ResumableHttpFileUploader uploader) - { - switch (uploader.getUploadState()) - { - case COMPLETE: - log.info("Upload Completed: " + videoName); - break; - case CLIENT_ERROR: - log.error("Upload Failed: " + videoName); - break; - case IN_PROGRESS: - log.info(videoName + String.format(" %3.0f", uploader.getProgress() * 100) + "%"); - break; - case NOT_STARTED: - log.info("Upload Not Started: " + videoName); - break; - } - } - } - -} diff --git a/source/java/org/alfresco/repo/publishing/youtube/YouTubePublishingHelper.java b/source/java/org/alfresco/repo/publishing/youtube/YouTubePublishingHelper.java index 223dd8de23..7438538f61 100644 --- a/source/java/org/alfresco/repo/publishing/youtube/YouTubePublishingHelper.java +++ b/source/java/org/alfresco/repo/publishing/youtube/YouTubePublishingHelper.java @@ -18,10 +18,12 @@ */ package org.alfresco.repo.publishing.youtube; +import java.io.Serializable; +import java.util.Map; + import org.alfresco.repo.node.encryption.MetadataEncryptor; import org.alfresco.repo.publishing.PublishingModel; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -30,42 +32,32 @@ import com.google.gdata.client.youtube.YouTubeService; public class YouTubePublishingHelper { private static final Log log = LogFactory.getLog(YouTubePublishingHelper.class); - private NodeService nodeService; private MetadataEncryptor encryptor; - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - public void setEncryptor(MetadataEncryptor encryptor) { this.encryptor = encryptor; } - public YouTubeService getYouTubeServiceForNode(NodeRef publishNode) + public YouTubeService getYouTubeServiceFromChannelProperties(Map channelProperties) { YouTubeService service = null; - if (nodeService.exists(publishNode)) + if (channelProperties != null) { - NodeRef parent = nodeService.getPrimaryParent(publishNode).getParentRef(); - if (nodeService.hasAspect(parent, YouTubePublishingModel.ASPECT_DELIVERY_CHANNEL)) + String youtubeUsername = (String) encryptor.decrypt(PublishingModel.PROP_CHANNEL_USERNAME, + channelProperties.get(PublishingModel.PROP_CHANNEL_USERNAME)); + String youtubePassword = (String) encryptor.decrypt(PublishingModel.PROP_CHANNEL_PASSWORD, + channelProperties.get(PublishingModel.PROP_CHANNEL_PASSWORD)); + service = new YouTubeService("Alfresco", + "AI39si78RHlniONCtnu9o8eBfwZToBAp2ZbbURm5eoJjj4gZi0LcxjDqJTzD35oYokmtFXbCo5ojofbimGnMlRbmNrh7-M7ZCw"); + try { - String youtubeUsername = (String) encryptor.decrypt(PublishingModel.PROP_CHANNEL_USERNAME, nodeService - .getProperty(parent, PublishingModel.PROP_CHANNEL_USERNAME)); - String youtubePassword = (String) encryptor.decrypt(PublishingModel.PROP_CHANNEL_PASSWORD, nodeService - .getProperty(parent, PublishingModel.PROP_CHANNEL_PASSWORD)); - service = new YouTubeService("Alfresco", - "AI39si78RHlniONCtnu9o8eBfwZToBAp2ZbbURm5eoJjj4gZi0LcxjDqJTzD35oYokmtFXbCo5ojofbimGnMlRbmNrh7-M7ZCw"); - try - { - service.setUserCredentials(youtubeUsername, youtubePassword); - } - catch (Exception e) - { - service = null; - log.error("Failed to connect to YouTube", e); - } + service.setUserCredentials(youtubeUsername, youtubePassword); + } + catch (Exception e) + { + service = null; + log.error("Failed to connect to YouTube", e); } } return service; diff --git a/source/java/org/alfresco/repo/publishing/youtube/YouTubeTest.java b/source/java/org/alfresco/repo/publishing/youtube/YouTubeTest.java index 9de852dc51..5f040a0dbb 100644 --- a/source/java/org/alfresco/repo/publishing/youtube/YouTubeTest.java +++ b/source/java/org/alfresco/repo/publishing/youtube/YouTubeTest.java @@ -31,8 +31,6 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.publishing.channels.Channel; import org.alfresco.service.cmr.publishing.channels.ChannelService; @@ -100,6 +98,9 @@ public class YouTubeTest extends BaseSpringTest //text "YOUR_USER_NAME" and "YOUR_PASSWORD" appear. public void xtestYouTubePublishAndUnpublishActions() throws Exception { + final String channelName = GUID.generate(); + final YouTubeChannelType channelType = (YouTubeChannelType) channelService.getChannelType(YouTubeChannelType.ID); + final NodeRef vidNode = transactionHelper.doInTransaction(new RetryingTransactionCallback() { public NodeRef execute() throws Throwable @@ -107,7 +108,7 @@ public class YouTubeTest extends BaseSpringTest Map props = new HashMap(); props.put(PublishingModel.PROP_CHANNEL_USERNAME, "YOUR_USER_NAME"); props.put(PublishingModel.PROP_CHANNEL_PASSWORD, "YOUR_PASSWORD"); - Channel channel = channelService.createChannel(YouTubeChannelType.ID, "YouTubeChannel", props); + Channel channel = channelService.createChannel(YouTubeChannelType.ID, channelName, props); NodeRef channelNode = channel.getNodeRef(); Resource videoFile = new ClassPathResource("test/alfresco/TestVideoFile.MP4"); @@ -128,9 +129,7 @@ public class YouTubeTest extends BaseSpringTest { public NodeRef execute() throws Throwable { - ActionService actionService = serviceRegistry.getActionService(); - Action publishAction = actionService.createAction(YouTubePublishAction.NAME); - actionService.executeAction(publishAction, vidNode); + channelType.publish(vidNode, channelService.getChannelByName(channelName).getProperties()); Map props = nodeService.getProperties(vidNode); Assert.assertTrue(nodeService.hasAspect(vidNode, YouTubePublishingModel.ASPECT_ASSET)); Assert.assertNotNull(props.get(PublishingModel.PROP_ASSET_ID)); @@ -138,8 +137,7 @@ public class YouTubeTest extends BaseSpringTest System.out.println("YouTube video: " + props.get(PublishingModel.PROP_ASSET_ID)); - Action unpublishAction = actionService.createAction(YouTubeUnpublishAction.NAME); - actionService.executeAction(unpublishAction, vidNode); + channelType.unpublish(vidNode, channelService.getChannelByName(channelName).getProperties()); props = nodeService.getProperties(vidNode); Assert.assertFalse(nodeService.hasAspect(vidNode, YouTubePublishingModel.ASPECT_ASSET)); Assert.assertNull(props.get(PublishingModel.PROP_ASSET_ID)); diff --git a/source/java/org/alfresco/repo/publishing/youtube/YouTubeUnpublishAction.java b/source/java/org/alfresco/repo/publishing/youtube/YouTubeUnpublishAction.java deleted file mode 100644 index 8159c06ef3..0000000000 --- a/source/java/org/alfresco/repo/publishing/youtube/YouTubeUnpublishAction.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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.youtube; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.List; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; -import org.alfresco.repo.publishing.PublishingModel; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ParameterDefinition; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import com.google.gdata.client.youtube.YouTubeService; -import com.google.gdata.data.youtube.VideoEntry; -import com.google.gdata.util.ServiceException; - -public class YouTubeUnpublishAction extends ActionExecuterAbstractBase -{ - private final static Log log = LogFactory.getLog(YouTubeUnpublishAction.class); - public static final String NAME = "unpublish_youtube"; - - private NodeService nodeService; - private YouTubePublishingHelper youTubeHelper; - - public void setYouTubeHelper(YouTubePublishingHelper youTubeHelper) - { - this.youTubeHelper = youTubeHelper; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - @Override - protected void executeImpl(Action action, NodeRef actionedUponNodeRef) - { - YouTubeService service = youTubeHelper.getYouTubeServiceForNode(actionedUponNodeRef); - if (service != null) - { - try - { - removeVideo(service, actionedUponNodeRef); - } - catch (Exception ex) - { - log.error("Failed to remove asset from YouTube", ex); - throw new AlfrescoRuntimeException("exception.publishing.youtube.unpublishFailed", ex); - } - } - } - - private void removeVideo(YouTubeService service, NodeRef nodeRef) throws MalformedURLException, IOException, - ServiceException - { - if (nodeService.hasAspect(nodeRef, YouTubePublishingModel.ASPECT_ASSET)) - { - String youtubeId = (String) nodeService.getProperty(nodeRef, PublishingModel.PROP_ASSET_ID); - if (youtubeId != null) - { - String videoEntryUrl = "https://gdata.youtube.com/feeds/api/users/default/uploads/" + youtubeId; - VideoEntry videoEntry = service.getEntry(new URL(videoEntryUrl), VideoEntry.class); - videoEntry.delete(); - nodeService.removeAspect(nodeRef, YouTubePublishingModel.ASPECT_ASSET); - nodeService.removeAspect(nodeRef, PublishingModel.ASPECT_ASSET); - } - } - } - - @Override - protected void addParameterDefinitions(List paramList) - { - } -} diff --git a/source/java/org/alfresco/service/cmr/publishing/PublishingService.java b/source/java/org/alfresco/service/cmr/publishing/PublishingService.java index 76a9aa2b54..b9dfbee78d 100644 --- a/source/java/org/alfresco/service/cmr/publishing/PublishingService.java +++ b/source/java/org/alfresco/service/cmr/publishing/PublishingService.java @@ -63,8 +63,16 @@ public interface PublishingService void cancelPublishingEvent(String id); /** - * Retrieve the publishing queue associated with this publishing environment - * @return A PublishingQueue object corresponding tho this environment's publishing queue + * A factory method to create an empty publishing package that can be populated before being passed into + * a call to the {@link PublishingQueue#scheduleNewEvent(PublishingDetails)} operation. + * @return A publishing package that can be populated before being placed on the publishing queue. */ - PublishingQueue getPublishingQueue(); + PublishingDetails createPublishingDetails(); + + /** + * Adds the supplied publishing package onto the queue. + * @param publishingDetails The publishing package that is to be enqueued + * @return The identifier of the newly scheduled event + */ + String scheduleNewEvent(PublishingDetails publishingDetails); } diff --git a/source/java/org/alfresco/service/cmr/publishing/channels/Channel.java b/source/java/org/alfresco/service/cmr/publishing/channels/Channel.java index 35d5e4ba56..6c04e8ce4d 100644 --- a/source/java/org/alfresco/service/cmr/publishing/channels/Channel.java +++ b/source/java/org/alfresco/service/cmr/publishing/channels/Channel.java @@ -56,9 +56,7 @@ public interface Channel Map getProperties(); - void publish(NodeRef nodeToPublish); - void unPublish(NodeRef nodeToUnpublish); - void updateStatus(String status, String nodeUrl); + void sendStatusUpdate(String status, String nodeUrl); /** * Returns the URL for some published content given the content node in the editorial environment. diff --git a/source/java/org/alfresco/service/cmr/publishing/channels/ChannelService.java b/source/java/org/alfresco/service/cmr/publishing/channels/ChannelService.java index 1e217897e9..254c2db0e0 100644 --- a/source/java/org/alfresco/service/cmr/publishing/channels/ChannelService.java +++ b/source/java/org/alfresco/service/cmr/publishing/channels/ChannelService.java @@ -23,6 +23,7 @@ import java.io.Serializable; import java.util.List; import java.util.Map; +import org.alfresco.repo.publishing.AbstractChannelType; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; @@ -38,7 +39,7 @@ public interface ChannelService * @param channelType The channel type to be registered. * @throws IllegalArgumentException if a channel type is already registered that has the same identifier as the supplied one */ - void register(ChannelType channelType); + void register(AbstractChannelType channelType); /** * Retrieve the channel type that has the specified identifier diff --git a/source/java/org/alfresco/service/cmr/publishing/channels/ChannelType.java b/source/java/org/alfresco/service/cmr/publishing/channels/ChannelType.java index ef945d41a1..277f253fe8 100644 --- a/source/java/org/alfresco/service/cmr/publishing/channels/ChannelType.java +++ b/source/java/org/alfresco/service/cmr/publishing/channels/ChannelType.java @@ -19,7 +19,6 @@ package org.alfresco.service.cmr.publishing.channels; -import java.io.Serializable; import java.util.Map; import java.util.Set; @@ -38,14 +37,12 @@ public interface ChannelType String getId(); QName getChannelNodeType(); - void publish(NodeRef nodeToPublish, Map properties); - void unpublish(NodeRef nodeToUnpublish, Map properties); - void updateStatus(Channel channel, String status, Map properties); - boolean canPublish(); boolean canUnpublish(); boolean canPublishStatusUpdates(); + void sendStatusUpdate(Channel channel, String status); + Set getSupportedMimeTypes(); Set getSupportedContentTypes();