Fixed ALF-10333: Publishing: Multiple publishing events are processed sequentially rather than in parallel

Publishing: Remove some operations from the Channel and ChannelType interface that really shouldn't be exposed. Also removed the PublishingQueue interface - the two operations it had are now on the PublishingService.
WQS: Removed obsolete references to publishing channels.

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@30794 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Brian Remmington
2011-09-27 11:23:19 +00:00
parent 590f665d63
commit 2fd91d87c2
49 changed files with 1274 additions and 1525 deletions

View File

@@ -13,10 +13,10 @@
<value>defaultAsyncAction</value>
</property>
<property name="corePoolSize">
<value>2</value>
<value>8</value>
</property>
<property name="maximumPoolSize">
<value>10</value>
<value>20</value>
</property>
</bean>

View File

@@ -56,6 +56,7 @@
<bean id="baseChannelType" class="org.alfresco.repo.publishing.AbstractChannelType" abstract="true" >
<property name="channelService" ref="channelService" />
<property name="encryptor" ref="metadataEncryptor" />
<property name="nodeService" ref="NodeService" />
</bean>
<bean id="channelHelper" class="org.alfresco.repo.publishing.ChannelHelper">
@@ -63,6 +64,8 @@
<property name="dictionaryService" ref="DictionaryService" />
<property name="fileFolderService" ref="FileFolderService" />
<property name="permissionService" ref="PermissionService" />
<property name="eventHelper" ref="publishingEventHelper" />
<property name="serviceRegistry" ref="ServiceRegistry" />
</bean>
<bean id="publishingRootObject" class="org.alfresco.repo.publishing.PublishingRootObject">
@@ -95,13 +98,12 @@
<!-- Publishing Event Processor -->
<bean id="publishingEventProcessor" class="org.alfresco.repo.publishing.PublishingEventProcessor">
<property name="channelHelper" ref="channelHelper" />
<property name="channelService" ref="channelService" />
<property name="publishingEventHelper" ref="publishingEventHelper" />
<property name="nodeService" ref="NodeService" />
<property name="behaviourFilter" ref="policyBehaviourFilter" />
<property name="urlShortener" ref="urlShortener" />
<property name="dictionaryService" ref="DictionaryService" />
<property name="transactionService" ref="TransactionService" />
</bean>
<!-- Publishing Service -->

View File

@@ -11,9 +11,10 @@
</bean>
<bean id="flickrDeliveryChannelType" class="org.alfresco.repo.publishing.flickr.FlickrChannelType" parent="baseChannelType" >
<property name="nodeService" ref="NodeService" />
<property name="actionService" ref="ActionService" />
<property name="connectionFactory" ref="flickrConnectionFactory" />
<property name="taggingService" ref="TaggingService" />
<property name="contentService" ref="ContentService" />
<property name="flickrHelper" ref="flickrPublishingHelper" />
</bean>
<bean id="flickrPublishingHelper" class="org.alfresco.repo.publishing.flickr.FlickrPublishingHelper">
@@ -26,18 +27,4 @@
<constructor-arg value="f7dafa571e8698ff93b08ebcb8c3de88" />
<constructor-arg value="e9a8d6072d4cb9e6" />
</bean>
<bean id="publish_flickr" parent="action-executer" class="org.alfresco.repo.publishing.flickr.FlickrPublishAction">
<property name="nodeService" ref="NodeService" />
<property name="taggingService" ref="TaggingService" />
<property name="contentService" ref="ContentService" />
<property name="flickrHelper" ref="flickrPublishingHelper" />
<property name="publicAction" value="false" />
</bean>
<bean id="unpublish_flickr" parent="action-executer" class="org.alfresco.repo.publishing.flickr.FlickrUnpublishAction">
<property name="nodeService" ref="NodeService" />
<property name="flickrHelper" ref="flickrPublishingHelper" />
<property name="publicAction" value="false" />
</bean>
</beans>

View File

@@ -11,34 +11,19 @@
</bean>
<bean id="slidesharePublishingHelper" class="org.alfresco.repo.publishing.slideshare.SlideSharePublishingHelper">
<property name="nodeService" ref="NodeService" />
<property name="slideshareConnector" ref="slideshareApiConnector" />
<property name="encryptor" ref="metadataEncryptor" />
</bean>
<bean id="slideshareApiConnector" class="com.benfante.jslideshare.SlideShareConnectorImpl">
<bean id="slideshareApiConnector" class="org.alfresco.repo.publishing.slideshare.SlideShareConnectorImpl">
<property name="apiKey" value="iXUZdaNl" />
<property name="sharedSecret" value="DLysO5tR" />
</bean>
<bean id="publish_slideshare" parent="action-executer" class="org.alfresco.repo.publishing.slideshare.SlideSharePublishAction">
<property name="nodeService" ref="NodeService" />
<bean id="slideshareDeliveryChannelType" class="org.alfresco.repo.publishing.slideshare.SlideShareChannelType" parent="baseChannelType" >
<property name="publishingHelper" ref="slidesharePublishingHelper" />
<property name="taggingService" ref="TaggingService" />
<property name="contentService" ref="ContentService" />
<property name="slideShareHelper" ref="slidesharePublishingHelper" />
<property name="publicAction" value="false" />
</bean>
<bean id="unpublish_slideshare" parent="action-executer" class="org.alfresco.repo.publishing.slideshare.SlideShareUnpublishAction">
<property name="nodeService" ref="NodeService" />
<property name="slideShareHelper" ref="slidesharePublishingHelper" />
<property name="publicAction" value="false" />
</bean>
<bean id="slideshareDeliveryChannelType" class="org.alfresco.repo.publishing.slideshare.SlideShareChannelType" parent="baseChannelType" >
<property name="actionService" ref="ActionService" />
<property name="nodeService" ref="NodeService" />
<property name="publishingHelper" ref="slidesharePublishingHelper" />
</bean>

View File

@@ -11,28 +11,12 @@
</bean>
<bean id="youtubePublishingHelper" class="org.alfresco.repo.publishing.youtube.YouTubePublishingHelper">
<property name="nodeService" ref="NodeService" />
<property name="encryptor" ref="metadataEncryptor" />
</bean>
<bean id="publish_youtube" parent="action-executer" class="org.alfresco.repo.publishing.youtube.YouTubePublishAction">
<property name="nodeService" ref="NodeService" />
<bean id="youtubeDeliveryChannelType" class="org.alfresco.repo.publishing.youtube.YouTubeChannelType" parent="baseChannelType" >
<property name="taggingService" ref="TaggingService" />
<property name="contentService" ref="ContentService" />
<property name="youTubeHelper" ref="youtubePublishingHelper" />
<property name="publicAction" value="false" />
</bean>
<bean id="unpublish_youtube" parent="action-executer" class="org.alfresco.repo.publishing.youtube.YouTubeUnpublishAction">
<property name="nodeService" ref="NodeService" />
<property name="youTubeHelper" ref="youtubePublishingHelper" />
<property name="publicAction" value="false" />
</bean>
<bean id="youtubeDeliveryChannelType" class="org.alfresco.repo.publishing.youtube.YouTubeChannelType" parent="baseChannelType" >
<property name="actionService" ref="ActionService" />
<property name="nodeService" ref="NodeService" />
</bean>
</beans>

View File

@@ -100,6 +100,10 @@
<constructor-arg value="org.alfresco.service.cmr.security.PermissionService" />
</bean>
<bean id="TransactionService" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.alfresco.service.transaction.TransactionService" />
</bean>
<bean id="metadataEncryptor" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.alfresco.repo.node.encryption.MetadataEncryptor" />
</bean>

View File

@@ -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<QName> getSupportedContentTypes()
{
return Collections.emptySet();
}
@Override
public Set<String> getSupportedMimeTypes()
{
return Collections.emptySet();
}
@Override
public void sendStatusUpdate(Channel channel, String status)
{
throw new UnsupportedOperationException();
}
@Override
public void publish(NodeRef nodeToPublish, Map<QName, Serializable> channelProperties)
{
throw new UnsupportedOperationException();
}
@Override
public void unpublish(NodeRef nodeToUnpublish, Map<QName, Serializable> 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;
}
}

View File

@@ -43,17 +43,11 @@ import org.springframework.social.oauth1.OAuthToken;
*/
public abstract class AbstractOAuth1ChannelType<A> extends AbstractChannelType
{
private NodeService nodeService;
private OAuth1ConnectionFactory<A> connectionFactory;
public Connection<A> getConnectionForPublishNode(NodeRef publishNode)
{
NodeRef channelNode = nodeService.getPrimaryParent(publishNode).getParentRef();
return getConnectionForChannel(channelNode);
}
public Connection<A> getConnectionForChannel(NodeRef channelNode)
protected Connection<A> getConnectionForChannel(NodeRef channelNode)
{
NodeService nodeService = getNodeService();
Connection<A> connection = null;
if (nodeService.exists(channelNode)
&& nodeService.hasAspect(channelNode, PublishingModel.ASPECT_OAUTH1_DELIVERY_CHANNEL))
@@ -73,11 +67,6 @@ public abstract class AbstractOAuth1ChannelType<A> 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<A> 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<A> extends AbstractChannelType
protected AuthStatus internalAcceptAuthorisation(Channel channel, Map<String, String[]> callbackHeaders,
Map<String, String[]> callbackParams)
{
NodeService nodeService = getNodeService();
AuthStatus authorised = AuthStatus.UNAUTHORISED;
String[] verifier = callbackParams.get(getOAuthVerifierParamName());
if (verifier != null)
@@ -158,12 +149,4 @@ public abstract class AbstractOAuth1ChannelType<A> extends AbstractChannelType
{
this.connectionFactory = connectionFactory;
}
/**
* @param nodeService the nodeService to set
*/
public final void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
}

View File

@@ -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;
@@ -71,15 +72,18 @@ public class ChannelHelper
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;
}
}

View File

@@ -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<QName> newAspects = snapshot.getAspects();
removeUnwantedAspects(publishedNode, newAspects);
Map<QName, Serializable> snapshotProps = snapshot.getProperties();
removeUnwantedProperties(publishedNode, snapshotProps);
// Add new properties
Map<QName, Serializable> newProps= new HashMap<QName, Serializable>(snapshotProps);
newProps.remove(ContentModel.PROP_NODE_UUID);
nodeService.setProperties(publishedNode, snapshotProps);
// Add new aspects
addAspects(publishedNode, newAspects);
List<ChildAssociationRef> 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<QName, Serializable> snapshotProps)
{
Map<QName, Serializable> publishProps = nodeService.getProperties(publishedNode);
Set<QName> propsToRemove = new HashSet<QName>(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<QName> newAspects)
{
Set<QName> 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<QName> aspects)
{
Set<QName> 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<QName, Serializable> 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<QName, Serializable> getPropertiesToPublish(NodeSnapshot snapshot)
{
Map<QName, Serializable> properties = snapshot.getProperties();
// Remove the Node Ref Id
Map<QName, Serializable> actualProps = new HashMap<QName, Serializable>(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);
}
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<QName, Serializable> channelProperties);
void unpublish(NodeRef nodeToUnpublish, Map<QName, Serializable> channelProperties);
}

View File

@@ -92,7 +92,7 @@ public class MockChannelType extends AbstractChannelType
* {@inheritDoc}
*/
@Override
public void updateStatus(Channel channel, String status, Map<QName, Serializable> properties)
public void sendStatusUpdate(Channel channel, String status)
{
//NOOP
}

View File

@@ -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)
{

View File

@@ -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);
}
}

View File

@@ -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<Void>()
{
@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,189 +133,25 @@ 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<QName> newAspects = snapshot.getAspects();
removeUnwantedAspects(publishedNode, newAspects);
Map<QName, Serializable> snapshotProps = snapshot.getProperties();
removeUnwantedProperties(publishedNode, snapshotProps);
// Add new properties
Map<QName, Serializable> newProps= new HashMap<QName, Serializable>(snapshotProps);
newProps.remove(ContentModel.PROP_NODE_UUID);
nodeService.setProperties(publishedNode, snapshotProps);
// Add new aspects
addAspects(publishedNode, newAspects);
List<ChildAssociationRef> assocs = nodeService.getChildAssocs(publishedNode, ASSOC_LAST_PUBLISHING_EVENT, RegexQNamePattern.MATCH_ALL);
for (ChildAssociationRef assoc : assocs)
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Void>()
{
nodeService.removeChildAssociation(assoc);
}
}
/**
* @param publishedNode
* @param snapshotProps
*/
private void removeUnwantedProperties(NodeRef publishedNode, Map<QName, Serializable> snapshotProps)
{
Map<QName, Serializable> publishProps = nodeService.getProperties(publishedNode);
Set<QName> propsToRemove = new HashSet<QName>(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<QName> newAspects)
{
Set<QName> 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<QName> aspects)
{
Set<QName> 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<QName, Serializable> 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<QName, Serializable> getPropertiesToPublish(NodeSnapshot snapshot)
{
Map<QName, Serializable> properties = snapshot.getProperties();
// Remove the Node Ref Id
Map<QName, Serializable> actualProps = new HashMap<QName, Serializable>(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;
}
}

View File

@@ -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);

View File

@@ -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<String> 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

View File

@@ -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;
}

View File

@@ -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
{

View File

@@ -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<QName> getSupportedContentTypes()
{
return Collections.emptySet();
}
@Override
public Set<String> getSupportedMimeTypes()
{
return Collections.emptySet();
}
@Override
public void publish(NodeRef nodeToPublish, Map<QName, Serializable> properties)
{
}
@Override
public void unpublish(NodeRef nodeToUnpublish, Map<QName, Serializable> properties)
{
}
@Override
public void updateStatus(Channel channel, String status, Map<QName, Serializable> properties)
public void sendStatusUpdate(Channel channel, String status)
{
Connection<Facebook> connection = publishingHelper.getFacebookConnectionForChannel(channel.getNodeRef());
connection.updateStatus(status);

View File

@@ -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<Flickr>
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<String> supportedMimeTypes = DEFAULT_SUPPORTED_MIME_TYPES;
public void setActionService(ActionService actionService)
{
this.actionService = actionService;
}
public void setSupportedMimeTypes(Set<String> mimeTypes)
{
supportedMimeTypes = Collections.unmodifiableSet(new TreeSet<String>(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<Flickr>
return ID;
}
@Override
public Set<QName> getSupportedContentTypes()
{
return Collections.emptySet();
}
@Override
public Set<String> getSupportedMimeTypes()
{
@@ -107,35 +126,85 @@ public class FlickrChannelType extends AbstractOAuth1ChannelType<Flickr>
}
@Override
public void publish(NodeRef nodeToPublish, Map<QName, Serializable> properties)
public void publish(NodeRef nodeToPublish, Map<QName, Serializable> channelProperties)
{
Action publishAction = actionService.createAction(FlickrPublishAction.NAME);
actionService.executeAction(publishAction, nodeToPublish);
}
@Override
public void unpublish(NodeRef nodeToUnpublish, Map<QName, Serializable> properties)
{
Action action = actionService.createAction(FlickrUnpublishAction.NAME);
actionService.executeAction(action, nodeToUnpublish);
}
@Override
public void updateStatus(Channel channel, String status, Map<QName, Serializable> 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<Flickr> 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<String> 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<QName, Serializable> 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<Flickr> 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

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<Flickr> 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<String> 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<ParameterDefinition> paramList)
{
// TODO Auto-generated method stub
}
}

View File

@@ -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<Flickr> getConnectionFromChannelProps(Map<QName,Serializable> channelProperties)
{
Connection<Flickr> 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<Flickr> getConnectionForPublishNode(NodeRef publishNode)
{
Connection<Flickr> 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;
}

View File

@@ -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<NodeRef>()
{
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<QName, Serializable> 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));

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<Flickr> 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<ParameterDefinition> paramList)
{
//Deliberately empty
}
}

View File

@@ -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;

View File

@@ -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<AlfrescoLinke
return ID;
}
@Override
public Set<QName> getSupportedContentTypes()
{
return Collections.emptySet();
}
@Override
public Set<String> getSupportedMimeTypes()
{
return Collections.emptySet();
}
@Override
public void publish(NodeRef nodeToPublish, Map<QName, Serializable> properties)
{
//NO-OP
}
@Override
public void unpublish(NodeRef nodeToUnpublish, Map<QName, Serializable> properties)
{
//NO-OP
}
@Override
public int getMaximumStatusLength()
{
@@ -104,7 +75,7 @@ public class LinkedInChannelType extends AbstractOAuth1ChannelType<AlfrescoLinke
}
@Override
public void updateStatus(Channel channel, String status, Map<QName, Serializable> properties)
public void sendStatusUpdate(Channel channel, String status)
{
NodeRef channelNode = new NodeRef(channel.getId());
Connection<AlfrescoLinkedIn> connection = getConnectionForChannel(channelNode);
@@ -118,6 +89,6 @@ public class LinkedInChannelType extends AbstractOAuth1ChannelType<AlfrescoLinke
@Override
public String getNodeUrl(NodeRef node)
{
throw new UnsupportedOperationException();
return null;
}
}

View File

@@ -199,7 +199,7 @@ public class SlideShareApiImpl implements SlideShareApi
addParameter(parameters, "username", username);
addParameter(parameters, "password", password);
addParameter(parameters, "slideshow_id", id);
return sendMessage(URL_DELETE_SLIDESHOW, parameters).getSlideShowId();
return sendGetMessage(URL_DELETE_SLIDESHOW, parameters).getSlideShowId();
}
private Map<String, String> addParameter(Map<String, String> parameters, String name, String value)

View File

@@ -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<QName> getSupportedContentTypes()
{
return Collections.emptySet();
}
@Override
public Set<String> getSupportedMimeTypes()
{
@@ -100,31 +121,152 @@ public class SlideShareChannelType extends AbstractChannelType
@Override
public void publish(NodeRef nodeToPublish, Map<QName, Serializable> properties)
{
Action publishAction = actionService.createAction(SlideSharePublishAction.NAME);
actionService.executeAction(publishAction, nodeToPublish);
NodeService nodeService = getNodeService();
Pair<String, String> 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<String> 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<QName, Serializable> properties)
{
Action unpublishAction = actionService.createAction(SlideShareUnpublishAction.NAME);
actionService.executeAction(unpublishAction, nodeToUnpublish);
}
@Override
public void updateStatus(Channel channel, String status, Map<QName, Serializable> 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<String, String> 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;
}
}

View File

@@ -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<String, String> parameters) throws IOException,
SlideShareErrorException
{
PostMethod method = new PostMethod(url);
method.addParameter("api_key", this.apiKey);
Iterator<Map.Entry<String, String>> entryIt = parameters.entrySet().iterator();
while (entryIt.hasNext())
{
Map.Entry<String, String> 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<String, String> parameters, Map<String, File> files)
throws IOException, SlideShareErrorException
{
PostMethod method = new PostMethod(url);
List<Part> partList = new ArrayList<Part>();
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<Map.Entry<String, String>> entryIt = parameters.entrySet().iterator();
while (entryIt.hasNext())
{
Map.Entry<String, String> entry = entryIt.next();
partList.add(createStringPart(entry.getKey(), entry.getValue()));
}
Iterator<Map.Entry<String, File>> entryFileIt = files.entrySet().iterator();
while (entryFileIt.hasNext())
{
Map.Entry<String, File> 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<String, String> 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<Map.Entry<String, String>> entryIt = parameters.entrySet().iterator();
while (entryIt.hasNext())
{
Map.Entry<String, String> 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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<String, String> 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<String> 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<ParameterDefinition> paramList)
{
}
}

View File

@@ -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<String, String> 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<String, String> getSlideShareCredentialsForNode(NodeRef publishNode)
public Pair<String, String> getSlideShareCredentialsFromChannelProperties(Map<QName, Serializable> channelProperties)
{
Pair<String, String> 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<String, String>(username, password);
}
}
result = new Pair<String, String>(username, password);
}
return result;
}

View File

@@ -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<NodeRef> nodes = transactionHelper.doInTransaction(new RetryingTransactionCallback<List<NodeRef>>()
{
public List<NodeRef> 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<QName, Serializable> 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<QName, Serializable> 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<QName, Serializable> 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;
}
});

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<String, String> 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<ParameterDefinition> paramList)
{
}
}

View File

@@ -99,7 +99,7 @@ public class TestChannelType1 extends AbstractChannelType
}
@Override
public void updateStatus(Channel channel, String status, Map<QName, Serializable> properties)
public void sendStatusUpdate(Channel channel, String status)
{
//Deliberately blank
}

View File

@@ -99,7 +99,7 @@ public class TestChannelType2 extends AbstractChannelType
}
@Override
public void updateStatus(Channel channel, String status, Map<QName, Serializable> properties)
public void sendStatusUpdate(Channel channel, String status)
{
//Deliberately blank
}

View File

@@ -99,7 +99,7 @@ public class TestChannelType3 extends AbstractChannelType
}
@Override
public void updateStatus(Channel channel, String status, Map<QName, Serializable> properties)
public void sendStatusUpdate(Channel channel, String status)
{
//Deliberately blank
}

View File

@@ -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<Twitter>
return ID;
}
@Override
public Set<QName> getSupportedContentTypes()
{
return Collections.emptySet();
}
@Override
public Set<String> getSupportedMimeTypes()
{
return Collections.emptySet();
}
@Override
public void publish(NodeRef nodeToPublish, Map<QName, Serializable> properties)
{
//NO-OP
}
@Override
public void unpublish(NodeRef nodeToUnpublish, Map<QName, Serializable> properties)
{
//NO-OP
}
@Override
public int getMaximumStatusLength()
{
@@ -98,7 +69,7 @@ public class TwitterChannelType extends AbstractOAuth1ChannelType<Twitter>
}
@Override
public void updateStatus(Channel channel, String status, Map<QName, Serializable> properties)
public void sendStatusUpdate(Channel channel, String status)
{
Connection<Twitter> connection = getConnectionForChannel(channel.getNodeRef());
if (log.isInfoEnabled())
@@ -111,7 +82,7 @@ public class TwitterChannelType extends AbstractOAuth1ChannelType<Twitter>
@Override
public String getNodeUrl(NodeRef node)
{
throw new UnsupportedOperationException();
return null;
}
}

View File

@@ -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<String> 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<String> supportedMimeTypes = DEFAULT_SUPPORTED_MIME_TYPES;
public final static String ID = "youtube";
private NodeService nodeService;
private ActionService actionService;
private YouTubePublishingHelper youTubeHelper;
private ContentService contentService;
private TaggingService taggingService;
public void setNodeService(NodeService nodeService)
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<String> supportedMimeTypes)
@@ -98,12 +133,6 @@ public class YouTubeChannelType extends AbstractChannelType
return ID;
}
@Override
public Set<QName> getSupportedContentTypes()
{
return Collections.emptySet();
}
@Override
public Set<String> getSupportedMimeTypes()
{
@@ -113,31 +142,182 @@ public class YouTubeChannelType extends AbstractChannelType
@Override
public void publish(NodeRef nodeToPublish, Map<QName, Serializable> 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<QName, Serializable> properties)
{
Action youtubeUnpublishAction = actionService.createAction(YouTubeUnpublishAction.NAME);
actionService.executeAction(youtubeUnpublishAction, nodeToUnpublish);
}
@Override
public void updateStatus(Channel channel, String status, Map<QName, Serializable> 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);
}
}
}
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<String> 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;
}
}
return url;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<String> 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<ParameterDefinition> 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;
}
}
}
}

View File

@@ -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<QName, Serializable> 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;

View File

@@ -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<NodeRef>()
{
public NodeRef execute() throws Throwable
@@ -107,7 +108,7 @@ public class YouTubeTest extends BaseSpringTest
Map<QName, Serializable> props = new HashMap<QName, Serializable>();
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<QName, Serializable> 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));

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<ParameterDefinition> paramList)
{
}
}

View File

@@ -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);
}

View File

@@ -56,9 +56,7 @@ public interface Channel
Map<QName, Serializable> 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.

View File

@@ -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

View File

@@ -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<QName, Serializable> properties);
void unpublish(NodeRef nodeToUnpublish, Map<QName, Serializable> properties);
void updateStatus(Channel channel, String status, Map<QName, Serializable> properties);
boolean canPublish();
boolean canUnpublish();
boolean canPublishStatusUpdates();
void sendStatusUpdate(Channel channel, String status);
Set<String> getSupportedMimeTypes();
Set<QName> getSupportedContentTypes();