diff --git a/config/alfresco/application-context-highlevel.xml b/config/alfresco/application-context-highlevel.xml index 46bc0a679c..25521ddc42 100644 --- a/config/alfresco/application-context-highlevel.xml +++ b/config/alfresco/application-context-highlevel.xml @@ -27,5 +27,6 @@ + diff --git a/config/alfresco/flickr-publishing-context.xml b/config/alfresco/flickr-publishing-context.xml new file mode 100644 index 0000000000..d9db9c9794 --- /dev/null +++ b/config/alfresco/flickr-publishing-context.xml @@ -0,0 +1,36 @@ + + + + + + + + alfresco/model/flickrPublishingModel.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/model/flickrPublishingModel.xml b/config/alfresco/model/flickrPublishingModel.xml new file mode 100644 index 0000000000..d8ee31e61d --- /dev/null +++ b/config/alfresco/model/flickrPublishingModel.xml @@ -0,0 +1,47 @@ + + + Alfresco Flickr Publishing Content Model + Alfresco + 2011-07-13 + 1.0 + + + + + + + + + + + + + + + Flickr Delivery Channel + Node type used to represent Flickr delivery channels + pub:DeliveryChannel + + pub:OAuth1DeliveryChannelAspect + + + + + + + + Flickr Asset + Applied to a node that has been published to Flickr + + + Flickr asset Id + d:text + + + Flickr Asset URL + d:text + + + + + diff --git a/config/alfresco/slideshare-publishing-context.xml b/config/alfresco/slideshare-publishing-context.xml index 7963c03c67..1e80b548bd 100644 --- a/config/alfresco/slideshare-publishing-context.xml +++ b/config/alfresco/slideshare-publishing-context.xml @@ -30,6 +30,7 @@ + diff --git a/config/alfresco/web-publishing-context.xml b/config/alfresco/web-publishing-context.xml index 8a10778ca0..8e341150eb 100644 --- a/config/alfresco/web-publishing-context.xml +++ b/config/alfresco/web-publishing-context.xml @@ -107,7 +107,6 @@ - diff --git a/config/test/alfresco/TestImageFile.png b/config/test/alfresco/TestImageFile.png new file mode 100644 index 0000000000..ba09d11aec Binary files /dev/null and b/config/test/alfresco/TestImageFile.png differ diff --git a/source/java/org/alfresco/repo/publishing/ChannelServiceImpl.java b/source/java/org/alfresco/repo/publishing/ChannelServiceImpl.java index 3cbb1acd01..d66b3e7df0 100644 --- a/source/java/org/alfresco/repo/publishing/ChannelServiceImpl.java +++ b/source/java/org/alfresco/repo/publishing/ChannelServiceImpl.java @@ -175,16 +175,18 @@ public class ChannelServiceImpl implements ChannelService /** * {@inheritDoc} */ - public void deleteChannel(String siteId, String channelName) + public void deleteChannel(Channel channel) { - Set containers = getAllChannelContainers(siteId); - for (NodeRef channelContainer : containers) + List allChannelNodes = new ArrayList(); + NodeRef editorialNode = channel.getNodeRef(); + allChannelNodes.add(editorialNode); + for (AssociationRef assoc : nodeService.getSourceAssocs(editorialNode, PublishingModel.ASSOC_EDITORIAL_CHANNEL)) { - NodeRef channel = nodeService.getChildByName(channelContainer, ContentModel.ASSOC_CONTAINS, channelName); - if (channel != null) - { - nodeService.deleteNode(channel); - } + allChannelNodes.add(assoc.getSourceRef()); + } + for (NodeRef channelNode : allChannelNodes) + { + nodeService.deleteNode(channelNode); } } diff --git a/source/java/org/alfresco/repo/publishing/ChannelServiceImplIntegratedTest.java b/source/java/org/alfresco/repo/publishing/ChannelServiceImplIntegratedTest.java index 743815db54..ab5aeee53f 100644 --- a/source/java/org/alfresco/repo/publishing/ChannelServiceImplIntegratedTest.java +++ b/source/java/org/alfresco/repo/publishing/ChannelServiceImplIntegratedTest.java @@ -97,7 +97,8 @@ public class ChannelServiceImplIntegratedTest extends AbstractPublishingIntegrat { testCreateChannel(); - channelService.deleteChannel(siteId, channelName); + Channel channel = channelService.getChannel(siteId, channelName); + channelService.deleteChannel(channel); List channels = channelService.getChannels(siteId); assertTrue(channels.isEmpty()); diff --git a/source/java/org/alfresco/repo/publishing/facebook/FacebookPublishingHelper.java b/source/java/org/alfresco/repo/publishing/facebook/FacebookPublishingHelper.java index 7356c4d0c8..03f07a4cad 100644 --- a/source/java/org/alfresco/repo/publishing/facebook/FacebookPublishingHelper.java +++ b/source/java/org/alfresco/repo/publishing/facebook/FacebookPublishingHelper.java @@ -24,10 +24,7 @@ import org.alfresco.service.cmr.repository.NodeService; import org.springframework.social.connect.Connection; import org.springframework.social.facebook.api.Facebook; import org.springframework.social.facebook.connect.FacebookConnectionFactory; -import org.springframework.social.oauth1.OAuthToken; import org.springframework.social.oauth2.AccessGrant; -import org.springframework.social.twitter.api.Twitter; -import org.springframework.social.twitter.connect.TwitterConnectionFactory; public class FacebookPublishingHelper { diff --git a/source/java/org/alfresco/repo/publishing/flickr/FlickrChannelType.java b/source/java/org/alfresco/repo/publishing/flickr/FlickrChannelType.java new file mode 100644 index 0000000000..f8cc949f32 --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/flickr/FlickrChannelType.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.publishing.flickr; + +import java.io.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; +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.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ParameterCheck; +import org.springframework.social.oauth1.AuthorizedRequestToken; +import org.springframework.social.oauth1.OAuth1Operations; +import org.springframework.social.oauth1.OAuth1Parameters; +import org.springframework.social.oauth1.OAuthToken; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +public class FlickrChannelType extends AbstractChannelType +{ + public final static String ID = "flickr"; + private NodeService nodeService; + private FlickrPublishingHelper publishingHelper; + private ActionService actionService; + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setPublishingHelper(FlickrPublishingHelper flickrPublishingHelper) + { + this.publishingHelper = flickrPublishingHelper; + } + + public void setActionService(ActionService actionService) + { + this.actionService = actionService; + } + + @Override + public boolean canPublish() + { + return true; + } + + @Override + public boolean canPublishStatusUpdates() + { + return false; + } + + @Override + public boolean canUnpublish() + { + return true; + } + + @Override + public QName getChannelNodeType() + { + return FlickrPublishingModel.TYPE_DELIVERY_CHANNEL; + } + + @Override + public String getId() + { + return ID; + } + + @Override + public Set getSupportedContentTypes() + { + return Collections.emptySet(); + } + + @Override + public Set getSupportedMimetypes() + { + return Collections.emptySet(); + } + + @Override + public void publish(NodeRef nodeToPublish, Map properties) + { + Action publishAction = actionService.createAction(FlickrPublishAction.NAME); + actionService.executeAction(publishAction, nodeToPublish); + } + + @Override + public void unpublish(NodeRef nodeToUnpublish, Map properties) + { + } + + @Override + public void updateStatus(Channel channel, String status, Map properties) + { + throw new UnsupportedOperationException(); + } + + @Override + public String getNodeUrl(NodeRef node) + { + String url = null; + if (node != null && nodeService.exists(node) && nodeService.hasAspect(node, FlickrPublishingModel.ASPECT_ASSET)) + { + url = (String)nodeService.getProperty(node, FlickrPublishingModel.PROP_ASSET_URL); + } + return url; + } + + @Override + public String getAuthorisationUrl(Channel channel, String callbackUrl) + { + ParameterCheck.mandatory("channel", channel); + ParameterCheck.mandatory("callbackUrl", callbackUrl); + if (!ID.equals(channel.getChannelType().getId())) + { + throw new IllegalArgumentException("Invalid channel type: " + channel.getChannelType().getId()); + } + OAuth1Operations oauthOperations = publishingHelper.getConnectionFactory().getOAuthOperations(); + OAuthToken requestToken = oauthOperations.fetchRequestToken(callbackUrl, null); + + NodeRef channelNodeRef = channel.getNodeRef(); + nodeService.setProperty(channelNodeRef, PublishingModel.PROP_OAUTH1_TOKEN_SECRET, requestToken.getSecret()); + nodeService.setProperty(channelNodeRef, PublishingModel.PROP_OAUTH1_TOKEN_VALUE, requestToken.getValue()); + + MultiValueMap params = new LinkedMultiValueMap(); + params.add("perms", "delete"); + OAuth1Parameters oauthParams = new OAuth1Parameters(callbackUrl, params); + return oauthOperations.buildAuthorizeUrl(requestToken.getValue(), oauthParams); + } + + @Override + public boolean acceptAuthorisationCallback(Channel channel, Map callbackHeaders, + Map callbackParams) + { + boolean authorised = false; + String[] verifier = callbackParams.get("oauth_verifier"); + if (verifier != null) + { + OAuth1Operations oauthOperations = publishingHelper.getConnectionFactory().getOAuthOperations(); + NodeRef channelNodeRef = channel.getNodeRef(); + + Map currentProps = nodeService.getProperties(channelNodeRef); + String tokenValue = (String) currentProps.get(PublishingModel.PROP_OAUTH1_TOKEN_VALUE); + String tokenSecret = (String) currentProps.get(PublishingModel.PROP_OAUTH1_TOKEN_SECRET); + OAuthToken token = new OAuthToken(tokenValue, tokenSecret); + OAuthToken accessToken = oauthOperations.exchangeForAccessToken(new AuthorizedRequestToken(token, verifier[0]), null); + + Map newProps = new HashMap(); + newProps.put(PublishingModel.PROP_OAUTH1_TOKEN_VALUE, accessToken.getValue()); + newProps.put(PublishingModel.PROP_OAUTH1_TOKEN_SECRET, accessToken.getSecret()); + getChannelService().updateChannel(channel, newProps); + authorised = true; + } + return authorised; + } +} diff --git a/source/java/org/alfresco/repo/publishing/flickr/FlickrPublishAction.java b/source/java/org/alfresco/repo/publishing/flickr/FlickrPublishAction.java new file mode 100644 index 0000000000..e639c748dc --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/flickr/FlickrPublishAction.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.publishing.flickr; + +import java.io.File; +import java.util.List; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; +import org.alfresco.repo.content.filestore.FileContentReader; +import org.alfresco.repo.publishing.flickr.springsocial.api.Flickr; +import org.alfresco.repo.publishing.flickr.springsocial.api.MediaOperations; +import org.alfresco.repo.publishing.flickr.springsocial.api.PhotoMetadata; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.tagging.TaggingService; +import org.alfresco.util.TempFileProvider; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.social.connect.Connection; + +public class FlickrPublishAction extends ActionExecuterAbstractBase +{ + private final static Log log = LogFactory.getLog(FlickrPublishAction.class); + + public static final String NAME = "publish_flickr"; + + private NodeService nodeService; + private ContentService contentService; + private TaggingService taggingService; + private FlickrPublishingHelper flickrHelper; + + public void setFlickrHelper(FlickrPublishingHelper helper) + { + this.flickrHelper = helper; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + public void setTaggingService(TaggingService taggingService) + { + this.taggingService = taggingService; + } + + @Override + protected void executeImpl(Action action, NodeRef nodeToPublish) + { + ContentReader reader = contentService.getReader(nodeToPublish, ContentModel.PROP_CONTENT); + if (reader.exists()) + { + File contentFile; + boolean deleteContentFileOnCompletion = false; + if (FileContentReader.class.isAssignableFrom(reader.getClass())) + { + // Grab the content straight from the content store if we can... + contentFile = ((FileContentReader) reader).getFile(); + } + else + { + // ...otherwise copy it to a temp file and use the copy... + File tempDir = TempFileProvider.getLongLifeTempDir("flickr"); + contentFile = TempFileProvider.createTempFile("flickr", "", tempDir); + reader.getContent(contentFile); + deleteContentFileOnCompletion = true; + } + try + { + Resource res = new FileSystemResource(contentFile); + Connection connection = flickrHelper.getConnectionForPublishNode(nodeToPublish); + + String name = (String) nodeService.getProperty(nodeToPublish, ContentModel.PROP_NAME); + String title = (String) nodeService.getProperty(nodeToPublish, ContentModel.PROP_TITLE); + if (title == null || title.length() == 0) + { + title = name; + } + String description = (String) nodeService.getProperty(nodeToPublish, ContentModel.PROP_DESCRIPTION); + if (description == null || description.length() == 0) + { + description = title; + } + + MediaOperations mediaOps = connection.getApi().mediaOperations(); + PhotoMetadata metadata = mediaOps.createPhotoMetadata(); + metadata.setTitle(title); + metadata.setDescription(description); + String id = mediaOps.postPhoto(res, metadata); + log.info("Posted image " + name + " to Flickr with id " + id); + nodeService.setProperty(nodeToPublish, FlickrPublishingModel.PROP_ASSET_ID, id); + } + finally + { + if (deleteContentFileOnCompletion) + { + contentFile.delete(); + } + } + } + } + + @Override + protected void addParameterDefinitions(List paramList) + { + // TODO Auto-generated method stub + + } +} diff --git a/source/java/org/alfresco/repo/publishing/flickr/FlickrPublishingHelper.java b/source/java/org/alfresco/repo/publishing/flickr/FlickrPublishingHelper.java new file mode 100644 index 0000000000..f7aa070b1a --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/flickr/FlickrPublishingHelper.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.publishing.flickr; + +import 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.springframework.social.connect.Connection; +import org.springframework.social.oauth1.OAuthToken; + +public class FlickrPublishingHelper +{ + private NodeService nodeService; + private FlickrConnectionFactory connectionFactory; + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setConnectionFactory(FlickrConnectionFactory connectionFactory) + { + this.connectionFactory = connectionFactory; + } + + public FlickrConnectionFactory getConnectionFactory() + { + return connectionFactory; + } + + public Connection getConnectionForPublishNode(NodeRef publishNode) + { + Connection connection = null; + NodeRef channelNode = nodeService.getPrimaryParent(publishNode).getParentRef(); + if (nodeService.exists(channelNode) + && nodeService.hasAspect(channelNode, PublishingModel.ASPECT_OAUTH1_DELIVERY_CHANNEL)) + { + String tokenValue = (String) nodeService.getProperty(channelNode, PublishingModel.PROP_OAUTH1_TOKEN_VALUE); + String tokenSecret = (String) 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); + } + } + return connection; + } + +} diff --git a/source/java/org/alfresco/repo/publishing/flickr/FlickrPublishingModel.java b/source/java/org/alfresco/repo/publishing/flickr/FlickrPublishingModel.java new file mode 100644 index 0000000000..c060aefc3a --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/flickr/FlickrPublishingModel.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.publishing.flickr; + +import org.alfresco.service.namespace.QName; + +/** + * @author Brian + * + */ +public interface FlickrPublishingModel +{ + public static final String NAMESPACE = "http://www.alfresco.org/model/publishing/flickr/1.0"; + public static final String PREFIX = "flickr"; + + public static final QName TYPE_DELIVERY_CHANNEL = QName.createQName(NAMESPACE, "DeliveryChannel"); + + public static final QName ASPECT_ASSET = QName.createQName(NAMESPACE, "AssetAspect"); + public static final QName PROP_ASSET_ID = QName.createQName(NAMESPACE, "assetId"); + public static final QName PROP_ASSET_URL = QName.createQName(NAMESPACE, "assetUrl"); +} diff --git a/source/java/org/alfresco/repo/publishing/flickr/FlickrTest.java b/source/java/org/alfresco/repo/publishing/flickr/FlickrTest.java new file mode 100644 index 0000000000..5f678bd6ea --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/flickr/FlickrTest.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.publishing.flickr; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.publishing.EnvironmentImpl; +import org.alfresco.repo.publishing.PublishingModel; +import org.alfresco.repo.publishing.PublishingQueueImpl; +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; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.cmr.site.SiteVisibility; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.BaseSpringTest; +import org.alfresco.util.GUID; +import org.apache.commons.fileupload.MultipartStream; +import org.junit.Assert; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +/** + * @author Brian + * + */ +public class FlickrTest extends BaseSpringTest +{ + protected ServiceRegistry serviceRegistry; + protected SiteService siteService; + protected FileFolderService fileFolderService; + protected NodeService nodeService; + protected String siteId; + protected PublishingQueueImpl queue; + protected EnvironmentImpl environment; + protected NodeRef docLib; + + private ChannelService channelService; + + private RetryingTransactionHelper transactionHelper; + + public void onSetUp() throws Exception + { + serviceRegistry = (ServiceRegistry) getApplicationContext().getBean("ServiceRegistry"); + channelService = (ChannelService) getApplicationContext().getBean("channelService"); + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + siteService = serviceRegistry.getSiteService(); + fileFolderService = serviceRegistry.getFileFolderService(); + nodeService = serviceRegistry.getNodeService(); + transactionHelper = serviceRegistry.getRetryingTransactionHelper(); + + siteId = GUID.generate(); + siteService.createSite("test", siteId, "Site created by publishing test", "Site created by publishing test", + SiteVisibility.PUBLIC); + docLib = siteService.createContainer(siteId, SiteService.DOCUMENT_LIBRARY, ContentModel.TYPE_FOLDER, null); + } + + public void onTearDown() + { + siteService.deleteSite(siteId); + } + + public void testBlank() + { + } + + //Note that this test isn't normally run, as it requires a valid Flickr OAuth token. + //To run it, remove the initial 'x' from the method name and set the appropriate values where the + //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 NodeRef contentNode = transactionHelper.doInTransaction(new RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + Map props = new HashMap(); + props.put(PublishingModel.PROP_OAUTH1_TOKEN_VALUE, "YOUR_OAUTH_TOKEN_VALUE"); + props.put(PublishingModel.PROP_OAUTH1_TOKEN_SECRET, "YOUR_OAUTH_TOKEN_SECRET"); + props.put(PublishingModel.PROP_AUTHORISATION_COMPLETE, Boolean.TRUE); + + Channel channel = channelService.createChannel(siteId, FlickrChannelType.ID, "FlickrTestChannel", 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); + + NodeRef channelNode = channel.getNodeRef(); + Resource file = new ClassPathResource("test/alfresco/TestImageFile.png"); + Map contentProps = new HashMap(); + contentProps.put(ContentModel.PROP_NAME, "Test Image"); + NodeRef contentNode = nodeService.createNode(channelNode, ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testImage"), + ContentModel.TYPE_CONTENT, contentProps).getChildRef(); + ContentService contentService = serviceRegistry.getContentService(); + ContentWriter writer = contentService.getWriter(contentNode, ContentModel.PROP_CONTENT, true); + writer.setMimetype(MimetypeMap.MIMETYPE_IMAGE_PNG); + writer.putContent(file.getFile()); + return contentNode; + } + }); + MultipartStream stream = new MultipartStream(null, null); + transactionHelper.doInTransaction(new RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + ActionService actionService = serviceRegistry.getActionService(); + Action publishAction = actionService.createAction(FlickrPublishAction.NAME); + actionService.executeAction(publishAction, contentNode); + Map props = nodeService.getProperties(contentNode); + Assert.assertTrue(nodeService.hasAspect(contentNode, FlickrPublishingModel.ASPECT_ASSET)); + Assert.assertNotNull(props.get(FlickrPublishingModel.PROP_ASSET_ID)); + + System.out.println("Asset id: " + props.get(FlickrPublishingModel.PROP_ASSET_ID)); + +// Action unpublishAction = actionService.createAction(YouTubeUnpublishAction.NAME); +// actionService.executeAction(unpublishAction, contentNode); +// props = nodeService.getProperties(contentNode); +// Assert.assertFalse(nodeService.hasAspect(contentNode, YouTubePublishingModel.ASPECT_ASSET)); +// Assert.assertNull(props.get(YouTubePublishingModel.PROP_ASSET_ID)); +// Assert.assertNull(props.get(YouTubePublishingModel.PROP_PLAYER_URL)); + return null; + } + }); + + } + +} diff --git a/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/Flickr.java b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/Flickr.java new file mode 100644 index 0000000000..f18c6521fc --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/Flickr.java @@ -0,0 +1,36 @@ +/* + * Copyright 2010 the original author or authors. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.alfresco.repo.publishing.flickr.springsocial.api; + +import org.alfresco.repo.publishing.flickr.springsocial.api.MediaOperations; +import org.alfresco.repo.publishing.flickr.springsocial.api.impl.FlickrTemplate; +import org.springframework.social.ApiBinding; + +/** + * Interface specifying a basic set of operations for interacting with Facebook. + * Implemented by {@link FlickrTemplate}. + * + * @author Craig Walls + */ +public interface Flickr extends ApiBinding +{ + boolean test(); + + /** + * API for performing operations on albums, photos, and videos. + */ + MediaOperations mediaOperations(); +} diff --git a/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/GraphApi.java b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/GraphApi.java new file mode 100644 index 0000000000..093d7162ed --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/GraphApi.java @@ -0,0 +1,121 @@ +/* + * Copyright 2011 the original author or authors. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.alfresco.repo.publishing.flickr.springsocial.api; + +import org.springframework.social.facebook.api.ImageType; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.ResponseExtractor; + +/** + * Defines low-level operations against Facebook's Graph API + * @author Craig Walls + */ +public interface GraphApi { + + /** + * Fetches an object, extracting it into the type via the given {@link ResponseExtractor}. + * Requires appropriate permission to fetch the object. + * @param objectId the Facebook object's ID + * @param type the Java type to fetch + * @return an Java object representing the requested Facebook object. + */ + T fetchObject(String objectId, Class type); + + /** + * Fetches an object, extracting it into the type via the given {@link ResponseExtractor}. + * Requires appropriate permission to fetch the object. + * @param objectId the Facebook object's ID + * @param type the Java type to fetch + * @param queryParameters query parameters to include in the request + * @return an Java object representing the requested Facebook object. + */ + T fetchObject(String objectId, Class type, MultiValueMap queryParameters); + + /** + * Fetches connections, extracting them into a Java type via the given {@link ResponseExtractor}. + * Requires appropriate permission to fetch the object connection. + * @param objectId the ID of the object to retrieve the connections for. + * @param connectionType the connection type. + * @param type the Java type to fetch + * @param fields the fields to include in the response. + * @return a list of Java objects representing the Facebook objects in the connections. + */ + T fetchConnections(String objectId, String connectionType, Class type, String... fields); + + /** + * Fetches connections, extracting them into a Java type via the given {@link ResponseExtractor}. + * Requires appropriate permission to fetch the object connection. + * @param objectId the ID of the object to retrieve the connections for. + * @param connectionType the connection type. + * @param type the Java type to fetch + * @param queryParameters query parameters to include in the request + * @return a list of Java objects representing the Facebook objects in the connections. + */ + T fetchConnections(String objectId, String connectionType, Class type, MultiValueMap queryParameters); + + /** + * Fetches an image as an array of bytes. + * @param objectId the object ID + * @param connectionType the connection type + * @param imageType the type of image to retrieve (eg., small, normal, large, or square) + * @return an image as an array of bytes. + */ + byte[] fetchImage(String objectId, String connectionType, ImageType imageType); + + /** + * Publishes data to an object's connection. + * Requires appropriate permission to publish to the object connection. + * @param objectId the object ID to publish to. + * @param connectionType the connection type to publish to. + * @param data the data to publish to the connection. + * @return the ID of the newly published object. + */ + String publish(String objectId, String connectionType, MultiValueMap data); + + /** + * Publishes data to an object's connection. + * Requires appropriate permission to publish to the object connection. + * This differs from publish() only in that it doesn't attempt to extract the ID from the response. + * This is because some publish operations do not return an ID in the response. + * @param objectId the object ID to publish to. + * @param connectionType the connection type to publish to. + * @param data the data to publish to the connection. + */ + void post(String objectId, String connectionType, MultiValueMap data); + + /** + * Deletes an object. + * Requires appropriate permission to delete the object. + * @param objectId the object ID + */ + void delete(String objectId); + + /** + * Deletes an object connection. + * Requires appropriate permission to delete the object connection. + * @param objectId the object ID + * @param connectionType the connection type + */ + void delete(String objectId, String connectionType); + + static final String GRAPH_API_URL = "https://graph.facebook.com/"; + + static final String OBJECT_URL = GRAPH_API_URL + "{objectId}"; + + static final String CONNECTION_URL = OBJECT_URL + "/{connection}"; + + +} diff --git a/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/MediaOperations.java b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/MediaOperations.java new file mode 100644 index 0000000000..83d40ca884 --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/MediaOperations.java @@ -0,0 +1,42 @@ +/* + * Copyright 2011 the original author or authors. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.alfresco.repo.publishing.flickr.springsocial.api; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.social.ApiException; +import org.springframework.social.InsufficientPermissionException; +import org.springframework.social.MissingAuthorizationException; + + +public interface MediaOperations { + + PhotoMetadata createPhotoMetadata(); + + /** + * Uploads a photo to an album created specifically for the application. + * Requires "publish_stream" permission. + * If no album exists for the application, it will be created. + * @param photo A {@link Resource} for the photo data. The given Resource must implement the getFilename() method (such as {@link FileSystemResource} or {@link ClassPathResource}). + * @return the ID of the photo. + * @throws ApiException if there is an error while communicating with Facebook. + * @throws InsufficientPermissionException if the user has not granted "publish_stream" permission. + * @throws MissingAuthorizationException if FacebookTemplate was not created with an access token. + */ + String postPhoto(Resource photo, PhotoMetadata metadata); + +} diff --git a/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/PhotoMetadata.java b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/PhotoMetadata.java new file mode 100644 index 0000000000..593e056842 --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/PhotoMetadata.java @@ -0,0 +1,14 @@ +package org.alfresco.repo.publishing.flickr.springsocial.api; + +public interface PhotoMetadata +{ + + void setDescription(String description); + + void setTitle(String title); + + String getDescription(); + + String getTitle(); + +} diff --git a/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/UserOperations.java b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/UserOperations.java new file mode 100644 index 0000000000..cfb11d8c61 --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/UserOperations.java @@ -0,0 +1,23 @@ +/* + * Copyright 2011 the original author or authors. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.alfresco.repo.publishing.flickr.springsocial.api; + + + + +public interface UserOperations { + +} diff --git a/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/AbstractFlickrOperations.java b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/AbstractFlickrOperations.java new file mode 100644 index 0000000000..d6f9e7da08 --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/AbstractFlickrOperations.java @@ -0,0 +1,34 @@ +/* + * Copyright 2011 the original author or authors. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.alfresco.repo.publishing.flickr.springsocial.api.impl; + +import org.springframework.social.MissingAuthorizationException; + +class AbstractFlickrOperations { + + private final boolean isAuthorized; + + public AbstractFlickrOperations(boolean isAuthorized) { + this.isAuthorized = isAuthorized; + } + + protected void requireAuthorization() { + if (!isAuthorized) { + throw new MissingAuthorizationException(); + } + } + +} diff --git a/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/FlickrErrorHandler.java b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/FlickrErrorHandler.java new file mode 100644 index 0000000000..f20a24dd28 --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/FlickrErrorHandler.java @@ -0,0 +1,144 @@ +/* + * Copyright 2011 the original author or authors. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.alfresco.repo.publishing.flickr.springsocial.api.impl; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Map; + +import org.springframework.http.HttpStatus; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.social.ExpiredAuthorizationException; +import org.springframework.social.InsufficientPermissionException; +import org.springframework.social.InternalServerErrorException; +import org.springframework.social.InvalidAuthorizationException; +import org.springframework.social.MissingAuthorizationException; +import org.springframework.social.NotAuthorizedException; +import org.springframework.social.OperationNotPermittedException; +import org.springframework.social.ResourceNotFoundException; +import org.springframework.social.RevokedAuthorizationException; +import org.springframework.social.UncategorizedApiException; +import org.springframework.social.facebook.api.NotAFriendException; +import org.springframework.social.facebook.api.ResourceOwnershipException; +import org.springframework.web.client.DefaultResponseErrorHandler; + +/** + * Subclass of {@link DefaultResponseErrorHandler} that handles errors from Facebook's + * Graph API, interpreting them into appropriate exceptions. + * @author Craig Walls + */ +class FlickrErrorHandler extends DefaultResponseErrorHandler { + + @Override + public void handleError(ClientHttpResponse response) throws IOException { + Map errorDetails = extractErrorDetailsFromResponse(response); + if (errorDetails == null) { + handleUncategorizedError(response, errorDetails); + } + + handleFacebookError(response.getStatusCode(), errorDetails); + + // if not otherwise handled, do default handling and wrap with UncategorizedApiException + handleUncategorizedError(response, errorDetails); + } + + @Override + public boolean hasError(ClientHttpResponse response) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(response.getBody())); + return super.hasError(response) || (reader.ready() && reader.readLine().startsWith("{\"error\":")); + } + + /** + * Examines the error data returned from Facebook and throws the most applicable exception. + * @param errorDetails a Map containing a "type" and a "message" corresponding to the Graph API's error response structure. + */ + void handleFacebookError(HttpStatus statusCode, Map errorDetails) { + // Can't trust the type to be useful. It's often OAuthException, even for things not OAuth-related. + // Can rely only on the message (which itself isn't very consistent). + String message = errorDetails.get("message"); + + if (statusCode == HttpStatus.OK) { + if (message.contains("Some of the aliases you requested do not exist")) { + throw new ResourceNotFoundException(message); + } + } else if (statusCode == HttpStatus.BAD_REQUEST) { + if (message.contains("Unknown path components")) { + throw new ResourceNotFoundException(message); + } else if (message.equals("An access token is required to request this resource.")) { + throw new MissingAuthorizationException(); + } else if (message.equals("An active access token must be used to query information about the current user.")) { + throw new MissingAuthorizationException(); + } else if (message.startsWith("Error validating access token")) { + if (message.contains("Session has expired at unix time")) { + throw new ExpiredAuthorizationException(); + } else if (message.contains("The session has been invalidated because the user has changed the password.")) { + throw new RevokedAuthorizationException(); + } else if (message.contains("The session is invalid because the user logged out.")) { + throw new RevokedAuthorizationException(); + } else if (message.contains("has not authorized application")) { + // Per https://developers.facebook.com/blog/post/500/, this could be in the message when the user removes the application. + // In reality, "The session has been invalidated because the user has changed the password." is what you get in that case. + // Leaving this check in place in case there FB does return this message (could be a bug in FB?) + throw new RevokedAuthorizationException(); + } else { + throw new InvalidAuthorizationException(message); + } + } else if (message.equals("Error validating application.")) { // Access token with incorrect app ID + throw new InvalidAuthorizationException(message); + } else if (message.equals("Invalid access token signature.")) { // Access token that fails signature validation + throw new InvalidAuthorizationException(message); + } + } else if (statusCode == HttpStatus.UNAUTHORIZED) { + throw new NotAuthorizedException(message); + } else if (statusCode == HttpStatus.FORBIDDEN) { + if (message.contains("Requires extended permission")) { + String requiredPermission = message.split(": ")[1]; + throw new InsufficientPermissionException(requiredPermission); + } else { + throw new OperationNotPermittedException(message); + } + } else if (statusCode == HttpStatus.INTERNAL_SERVER_ERROR) { + if (message.equals("User must be an owner of the friendlist")) { // watch for pattern in similar message in other resources + throw new ResourceOwnershipException(message); + } else if (message.equals("The member must be a friend of the current user.")) { + throw new NotAFriendException(message); + } else { + throw new InternalServerErrorException(message); + } + } + } + + private void handleUncategorizedError(ClientHttpResponse response, Map errorDetails) { + try { + super.handleError(response); + } catch (Exception e) { + if (errorDetails != null) { + throw new UncategorizedApiException(errorDetails.get("message"), e); + } else { + throw new UncategorizedApiException("No error details from Facebook", e); + } + } + } + + /* + * Attempts to extract Facebook error details from the response. + * Returns null if the response doesn't match the expected JSON error response. + */ + private Map extractErrorDetailsFromResponse(ClientHttpResponse response) throws IOException { + return null; + } +} diff --git a/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/FlickrTemplate.java b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/FlickrTemplate.java new file mode 100644 index 0000000000..5bb53cf314 --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/FlickrTemplate.java @@ -0,0 +1,114 @@ +/* + * Copyright 2010 the original author or authors. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.alfresco.repo.publishing.flickr.springsocial.api.impl; + +import java.net.URI; +import java.util.List; + +import org.alfresco.repo.publishing.flickr.springsocial.api.Flickr; +import org.alfresco.repo.publishing.flickr.springsocial.api.MediaOperations; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.converter.ByteArrayHttpMessageConverter; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.social.NotAuthorizedException; +import org.springframework.social.oauth1.AbstractOAuth1ApiBinding; +import org.springframework.social.support.ClientHttpRequestFactorySelector; +import org.springframework.social.support.URIBuilder; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +/** + *

This is the central class for interacting with Facebook.

+ *

+ * There are some operations, such as searching, that do not require OAuth + * authentication. In those cases, you may use a {@link FlickrTemplate} that is + * created through the default constructor and without any OAuth details. + * Attempts to perform secured operations through such an instance, however, + * will result in {@link NotAuthorizedException} being thrown. + *

+ * @author Craig Walls + */ +public class FlickrTemplate extends AbstractOAuth1ApiBinding implements Flickr { + private final String REST_ENDPOINT = "http://api.flickr.com/services/rest/"; + private String consumerKey; + + private MediaOperations mediaOperations; + + /** + * Create a new instance of FacebookTemplate. + * This constructor creates a new FacebookTemplate able to perform unauthenticated operations against Facebook's Graph API. + * Some operations do not require OAuth authentication. + * For example, retrieving a specified user's profile or feed does not require authentication (although the data returned will be limited to what is publicly available). + * A FacebookTemplate created with this constructor will support those operations. + * Those operations requiring authentication will throw {@link NotAuthorizedException}. + */ + public FlickrTemplate() { + initialize(); + } + + /** + * Create a new instance of FacebookTemplate. + * This constructor creates the FacebookTemplate using a given access token. + * @param accessToken An access token given by Facebook after a successful OAuth 2 authentication (or through Facebook's JS library). + */ + public FlickrTemplate(String consumerKey, String consumerSecret, String accessToken, String accessTokenSecret) { + super(consumerKey, consumerSecret, accessToken, accessTokenSecret); + this.consumerKey = consumerKey; + initialize(); + } + + private void initSubApis() { + mediaOperations = new MediaTemplate(consumerKey, getRestTemplate(), isAuthorized()); + } + + @Override + public void setRequestFactory(ClientHttpRequestFactory requestFactory) { + // Wrap the request factory with a BufferingClientHttpRequestFactory so that the error handler can do repeat reads on the response.getBody() + super.setRequestFactory(ClientHttpRequestFactorySelector.bufferRequests(requestFactory)); + } + + public MediaOperations mediaOperations() { + return mediaOperations; + } + + @Override + protected List> getMessageConverters() { + List> messageConverters = super.getMessageConverters(); + messageConverters.add(new ByteArrayHttpMessageConverter()); + return messageConverters; + } + + // private helpers + private void initialize() { + getRestTemplate().setErrorHandler(new FlickrErrorHandler()); + // Wrap the request factory with a BufferingClientHttpRequestFactory so that the error handler can do repeat reads on the response.getBody() + super.setRequestFactory(ClientHttpRequestFactorySelector.bufferRequests(getRestTemplate().getRequestFactory())); + initSubApis(); + } + + @Override + public boolean test() + { + MultiValueMap params = new LinkedMultiValueMap(); + params.add("api_key", consumerKey); + params.add("format", "json"); + params.add("method", "flickr.test.login"); + params.add("nojsoncallback", "1"); + URI uri = URIBuilder.fromUri(REST_ENDPOINT).queryParams(params).build(); + getRestTemplate().getForObject(uri, String.class); + return true; + } +} diff --git a/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/MediaTemplate.java b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/MediaTemplate.java new file mode 100644 index 0000000000..238fdfaf26 --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/MediaTemplate.java @@ -0,0 +1,61 @@ +/* + * Copyright 2011 the original author or authors. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.alfresco.repo.publishing.flickr.springsocial.api.impl; + +import java.net.URI; + +import org.alfresco.repo.publishing.flickr.springsocial.api.MediaOperations; +import org.alfresco.repo.publishing.flickr.springsocial.api.PhotoMetadata; +import org.alfresco.repo.publishing.flickr.springsocial.api.impl.AbstractFlickrOperations; +import org.springframework.core.io.Resource; +import org.springframework.social.support.URIBuilder; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +class MediaTemplate extends AbstractFlickrOperations implements MediaOperations +{ + private final RestTemplate restTemplate; + private String consumerKey; + + public MediaTemplate(String consumerKey, RestTemplate restTemplate, boolean isAuthorizedForUser) + { + super(isAuthorizedForUser); + this.restTemplate = restTemplate; + this.consumerKey = consumerKey; + } + + public String postPhoto(Resource photo, PhotoMetadata metadata) + { + requireAuthorization(); + MultiValueMap parts = new LinkedMultiValueMap(); + parts.set("api_key", consumerKey); + if (metadata.getDescription() != null) + parts.set("description", metadata.getDescription()); + parts.set("photo", photo); + if (metadata.getTitle() != null) + parts.set("title", metadata.getTitle()); + URI uri = URIBuilder.fromUri("http://api.flickr.com/services/upload/").build(); + String response = restTemplate.postForObject(uri, parts, String.class); + return (String) response; + } + + @Override + public PhotoMetadata createPhotoMetadata() + { + return new PhotoMetadataImpl(); + } +} diff --git a/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/PhotoMetadataImpl.java b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/PhotoMetadataImpl.java new file mode 100644 index 0000000000..bbbffc21d1 --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/PhotoMetadataImpl.java @@ -0,0 +1,29 @@ +package org.alfresco.repo.publishing.flickr.springsocial.api.impl; + +import org.alfresco.repo.publishing.flickr.springsocial.api.PhotoMetadata; + +public class PhotoMetadataImpl implements PhotoMetadata +{ + private String title; + private String description; + + public String getTitle() + { + return title; + } + + public void setTitle(String title) + { + this.title = title; + } + + public String getDescription() + { + return description; + } + + public void setDescription(String description) + { + this.description = description; + } +} diff --git a/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/UserTemplate.java b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/UserTemplate.java new file mode 100644 index 0000000000..5c27899844 --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/UserTemplate.java @@ -0,0 +1,27 @@ +/* + * Copyright 2011 the original author or authors. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.alfresco.repo.publishing.flickr.springsocial.api.impl; + +import org.alfresco.repo.publishing.flickr.springsocial.api.UserOperations; + +class UserTemplate extends AbstractFlickrOperations implements UserOperations { + + public UserTemplate(boolean isAuthorized) + { + super(isAuthorized); + } + +} diff --git a/source/java/org/alfresco/repo/publishing/flickr/springsocial/connect/FlickrAdapter.java b/source/java/org/alfresco/repo/publishing/flickr/springsocial/connect/FlickrAdapter.java new file mode 100644 index 0000000000..21068b2e6a --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/flickr/springsocial/connect/FlickrAdapter.java @@ -0,0 +1,60 @@ +/* + * Copyright 2011 the original author or authors. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.alfresco.repo.publishing.flickr.springsocial.connect; + +import org.alfresco.repo.publishing.flickr.springsocial.api.Flickr; +import org.springframework.social.ApiException; +import org.springframework.social.connect.ApiAdapter; +import org.springframework.social.connect.ConnectionValues; +import org.springframework.social.connect.UserProfile; +import org.springframework.social.connect.UserProfileBuilder; + +/** + * Facebook ApiAdapter implementation. + * + * @author Keith Donald + */ +public class FlickrAdapter implements ApiAdapter +{ + + public boolean test(Flickr flickr) + { + try + { + flickr.test(); + return true; + } + catch (ApiException e) + { + return false; + } + } + + public void setConnectionValues(Flickr facebook, ConnectionValues values) + { + } + + public UserProfile fetchUserProfile(Flickr facebook) + { + return new UserProfileBuilder().setName("Brian").setFirstName("Brian").setLastName( + "Brian").setEmail("Brian").setUsername("Brian").build(); + } + + public void updateStatus(Flickr facebook, String message) + { + } + +} diff --git a/source/java/org/alfresco/repo/publishing/flickr/springsocial/connect/FlickrConnectionFactory.java b/source/java/org/alfresco/repo/publishing/flickr/springsocial/connect/FlickrConnectionFactory.java new file mode 100644 index 0000000000..973dceaeae --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/flickr/springsocial/connect/FlickrConnectionFactory.java @@ -0,0 +1,28 @@ +/* + * Copyright 2011 the original author or authors. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.alfresco.repo.publishing.flickr.springsocial.connect; + +import org.alfresco.repo.publishing.flickr.springsocial.api.Flickr; +import org.alfresco.repo.publishing.flickr.springsocial.connect.FlickrAdapter; +import org.springframework.social.connect.support.OAuth1ConnectionFactory; + +public class FlickrConnectionFactory extends OAuth1ConnectionFactory { + + public FlickrConnectionFactory(String consumerKey, String consumerSecret) { + super("flickr", new FlickrServiceProvider(consumerKey, consumerSecret), new FlickrAdapter()); + } + +} diff --git a/source/java/org/alfresco/repo/publishing/flickr/springsocial/connect/FlickrServiceProvider.java b/source/java/org/alfresco/repo/publishing/flickr/springsocial/connect/FlickrServiceProvider.java new file mode 100644 index 0000000000..394eea4aca --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/flickr/springsocial/connect/FlickrServiceProvider.java @@ -0,0 +1,36 @@ +/* + * Copyright 2010 the original author or authors. + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.alfresco.repo.publishing.flickr.springsocial.connect; + +import org.alfresco.repo.publishing.flickr.springsocial.api.Flickr; +import org.alfresco.repo.publishing.flickr.springsocial.api.impl.FlickrTemplate; +import org.springframework.social.oauth1.AbstractOAuth1ServiceProvider; +import org.springframework.social.oauth1.OAuth1Template; + +public class FlickrServiceProvider extends AbstractOAuth1ServiceProvider { + + public FlickrServiceProvider(String consumerKey, String consumerSecret) { + super(consumerKey, consumerSecret, new OAuth1Template(consumerKey, consumerSecret, + "http://www.flickr.com/services/oauth/request_token", + "http://www.flickr.com/services/oauth/authorize", + "http://www.flickr.com/services/oauth/access_token")); + } + + public Flickr getApi(String accessToken, String secret) { + return new FlickrTemplate(getConsumerKey(), getConsumerSecret(), accessToken, secret); + } + +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/publishing/flickr/springsocial/connect/package-info.java b/source/java/org/alfresco/repo/publishing/flickr/springsocial/connect/package-info.java new file mode 100644 index 0000000000..a1b3625e99 --- /dev/null +++ b/source/java/org/alfresco/repo/publishing/flickr/springsocial/connect/package-info.java @@ -0,0 +1,4 @@ +/** + * Flickr service provider connection repository and API adapter implementations. + */ +package org.alfresco.repo.publishing.flickr.springsocial.connect; diff --git a/source/java/org/alfresco/repo/publishing/slideshare/SlideShareChannelType.java b/source/java/org/alfresco/repo/publishing/slideshare/SlideShareChannelType.java index db2d8ffa0f..cc5bdb0669 100644 --- a/source/java/org/alfresco/repo/publishing/slideshare/SlideShareChannelType.java +++ b/source/java/org/alfresco/repo/publishing/slideshare/SlideShareChannelType.java @@ -22,9 +22,7 @@ import java.io.Serializable; import java.util.Collections; import java.util.Map; import java.util.Set; -import java.util.TreeSet; -import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.publishing.AbstractChannelType; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; @@ -36,28 +34,11 @@ import org.alfresco.service.namespace.QName; public class SlideShareChannelType extends AbstractChannelType { public final static String ID = "slideshare"; - private final static Set DEFAULT_MIME_TYPES = new TreeSet(); private NodeService nodeService; private ActionService actionService; - private Set permittedMimeTypes = Collections.unmodifiableSet(DEFAULT_MIME_TYPES); - - static - { - DEFAULT_MIME_TYPES.add(MimetypeMap.MIMETYPE_PPT); - DEFAULT_MIME_TYPES.add(MimetypeMap.MIMETYPE_PDF); - DEFAULT_MIME_TYPES.add(MimetypeMap.MIMETYPE_OPENDOCUMENT_PRESENTATION); - DEFAULT_MIME_TYPES.add(MimetypeMap.MIMETYPE_OPENXML_PRESENTATION); - DEFAULT_MIME_TYPES.add(MimetypeMap.MIMETYPE_IWORK_KEYNOTE); - DEFAULT_MIME_TYPES.add(MimetypeMap.MIMETYPE_IWORK_PAGES); - DEFAULT_MIME_TYPES.add(MimetypeMap.MIMETYPE_TEXT_PLAIN); - DEFAULT_MIME_TYPES.add(MimetypeMap.MIMETYPE_OPENDOCUMENT_TEXT); - DEFAULT_MIME_TYPES.add(MimetypeMap.MIMETYPE_TEXT_CSV); - DEFAULT_MIME_TYPES.add(MimetypeMap.MIMETYPE_EXCEL); - DEFAULT_MIME_TYPES.add(MimetypeMap.MIMETYPE_OPENXML_WORDPROCESSING); - DEFAULT_MIME_TYPES.add(MimetypeMap.MIMETYPE_OPENDOCUMENT_SPREADSHEET); - } - + private SlideSharePublishingHelper publishingHelper; + public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; @@ -68,13 +49,9 @@ public class SlideShareChannelType extends AbstractChannelType this.actionService = actionService; } - public void setPermittedMimeTypes(Set permittedMimeTypes) + public void setPublishingHelper(SlideSharePublishingHelper publishingHelper) { - if (permittedMimeTypes == null) - { - permittedMimeTypes = Collections.emptySet(); - } - this.permittedMimeTypes = Collections.unmodifiableSet(permittedMimeTypes); + this.publishingHelper = publishingHelper; } @Override @@ -116,7 +93,7 @@ public class SlideShareChannelType extends AbstractChannelType @Override public Set getSupportedMimetypes() { - return permittedMimeTypes; + return publishingHelper.getAllowedMimeTypes().keySet(); } @Override diff --git a/source/java/org/alfresco/repo/publishing/slideshare/SlideSharePublishAction.java b/source/java/org/alfresco/repo/publishing/slideshare/SlideSharePublishAction.java index a8020d03e0..6aec28dfdc 100644 --- a/source/java/org/alfresco/repo/publishing/slideshare/SlideSharePublishAction.java +++ b/source/java/org/alfresco/repo/publishing/slideshare/SlideSharePublishAction.java @@ -24,7 +24,6 @@ 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.service.cmr.action.Action; import org.alfresco.service.cmr.action.ParameterDefinition; import org.alfresco.service.cmr.repository.ContentReader; @@ -38,6 +37,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.benfante.jslideshare.SlideShareAPI; +import com.benfante.jslideshare.messages.Slideshow; public class SlideSharePublishAction extends ActionExecuterAbstractBase { @@ -84,20 +84,19 @@ public class SlideSharePublishAction extends ActionExecuterAbstractBase if (reader.exists()) { File contentFile; + String mime = reader.getMimetype(); + + String extension = slideShareHelper.getAllowedMimeTypes().get(mime); + if (extension == null) extension = ""; + 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("slideshare"); - contentFile = TempFileProvider.createTempFile("slideshare", "", tempDir); - reader.getContent(contentFile); - deleteContentFileOnCompletion = true; - } + + //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; String name = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); String title = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE); @@ -121,8 +120,17 @@ public class SlideSharePublishAction extends ActionExecuterAbstractBase String assetId = api.uploadSlideshow(usernamePassword.getFirst(), usernamePassword.getSecond(), title, contentFile, description, tags.toString(), false, false, false, false, false); -// String url = api.getSlideshow(assetId).getPermalink(); + String url = null; + Slideshow slides = api.getSlideshow(assetId); + if (slides != null) + { + url = slides.getPermalink(); + if (log.isInfoEnabled()) + { + log.info("SlideShare has provided a URL for asset " + assetId + ": " + url); + } + } if (log.isInfoEnabled()) { log.info("File " + name + " has been published to SlideShare with id " + assetId + " at URL " + url); diff --git a/source/java/org/alfresco/repo/publishing/slideshare/SlideSharePublishingHelper.java b/source/java/org/alfresco/repo/publishing/slideshare/SlideSharePublishingHelper.java index ad1c1e6aa8..f0283688e8 100644 --- a/source/java/org/alfresco/repo/publishing/slideshare/SlideSharePublishingHelper.java +++ b/source/java/org/alfresco/repo/publishing/slideshare/SlideSharePublishingHelper.java @@ -18,6 +18,11 @@ */ package org.alfresco.repo.publishing.slideshare; +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; + +import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.publishing.PublishingModel; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -28,6 +33,24 @@ import com.benfante.jslideshare.SlideShareConnector; public class SlideSharePublishingHelper { + private final static Map DEFAULT_MIME_TYPES = new TreeMap(); + static + { + DEFAULT_MIME_TYPES.put(MimetypeMap.MIMETYPE_PPT, ".ppt"); + DEFAULT_MIME_TYPES.put(MimetypeMap.MIMETYPE_PDF, ".pdf"); + DEFAULT_MIME_TYPES.put(MimetypeMap.MIMETYPE_OPENDOCUMENT_PRESENTATION, ".odp"); + DEFAULT_MIME_TYPES.put(MimetypeMap.MIMETYPE_OPENXML_PRESENTATION, ".pptx"); + DEFAULT_MIME_TYPES.put(MimetypeMap.MIMETYPE_IWORK_KEYNOTE, ""); + DEFAULT_MIME_TYPES.put(MimetypeMap.MIMETYPE_IWORK_PAGES, ""); + DEFAULT_MIME_TYPES.put(MimetypeMap.MIMETYPE_TEXT_PLAIN, ".txt"); + DEFAULT_MIME_TYPES.put(MimetypeMap.MIMETYPE_OPENDOCUMENT_TEXT, ".odt"); + DEFAULT_MIME_TYPES.put(MimetypeMap.MIMETYPE_TEXT_CSV, ".csv"); + DEFAULT_MIME_TYPES.put(MimetypeMap.MIMETYPE_EXCEL, ".xls"); + DEFAULT_MIME_TYPES.put(MimetypeMap.MIMETYPE_OPENXML_WORDPROCESSING, ".docx"); + DEFAULT_MIME_TYPES.put(MimetypeMap.MIMETYPE_OPENDOCUMENT_SPREADSHEET, ".ods"); + } + + private Map allowedMimeTypes = Collections.unmodifiableMap(DEFAULT_MIME_TYPES); private NodeService nodeService; private SlideShareConnector slideshareConnector; @@ -41,6 +64,16 @@ public class SlideSharePublishingHelper this.slideshareConnector = slideshareConnector; } + public Map getAllowedMimeTypes() + { + return allowedMimeTypes; + } + + public void setAllowedMimeTypes(Map allowedMimeTypes) + { + this.allowedMimeTypes = Collections.unmodifiableMap(allowedMimeTypes); + } + public SlideShareAPI getSlideShareApi() { return createApiObject(); diff --git a/source/java/org/alfresco/repo/urlshortening/BitlyUrlShortenerImpl.java b/source/java/org/alfresco/repo/urlshortening/BitlyUrlShortenerImpl.java index f5e321af74..d3cf3d4fea 100644 --- a/source/java/org/alfresco/repo/urlshortening/BitlyUrlShortenerImpl.java +++ b/source/java/org/alfresco/repo/urlshortening/BitlyUrlShortenerImpl.java @@ -17,7 +17,7 @@ public class BitlyUrlShortenerImpl implements UrlShortener { private static final Log log = LogFactory.getLog(BitlyUrlShortenerImpl.class); - private int urlLength; + private int urlLength = 20; private String username; private String apiKey = "R_ca15c6c89e9b25ccd170bafd209a0d4f"; private HttpClient httpClient; diff --git a/source/java/org/alfresco/service/cmr/publishing/channels/ChannelService.java b/source/java/org/alfresco/service/cmr/publishing/channels/ChannelService.java index 8015ef1dce..ef7b72cc46 100644 --- a/source/java/org/alfresco/service/cmr/publishing/channels/ChannelService.java +++ b/source/java/org/alfresco/service/cmr/publishing/channels/ChannelService.java @@ -64,11 +64,10 @@ public interface ChannelService Channel createChannel(String siteId, String channelTypeId, String name, Map properties); /** - * Remove the channel with the specified name on the specified Share site. - * @param siteId The identifier of the Share site that contains the channel to be deleted. - * @param channelName The name of the channel that is to be deleted. + * Remove the specified channel. + * @param channel The channel to delete. */ - void deleteChannel(String siteId, String channelName); + void deleteChannel(Channel channel); /** * Rename the specified channel