From aeb467a3d87e762e26a224163ac9fc29af6af6c1 Mon Sep 17 00:00:00 2001 From: Brian Remmington Date: Fri, 15 Jul 2011 15:47:26 +0000 Subject: [PATCH] Publishing: - Added start of the Flickr channel. Channel creation and authorisation is complete (written as a new Spring Social module). API not currently working. - Fixed a problem with SlideShare API: it relies on the file extension to determine the type of the file, so needed to alter the way the files are sent. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@29085 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../application-context-highlevel.xml | 1 + config/alfresco/flickr-publishing-context.xml | 36 ++++ .../alfresco/model/flickrPublishingModel.xml | 47 +++++ .../slideshare-publishing-context.xml | 1 + config/alfresco/web-publishing-context.xml | 1 - config/test/alfresco/TestImageFile.png | Bin 0 -> 84949 bytes .../repo/publishing/ChannelServiceImpl.java | 18 +- .../ChannelServiceImplIntegratedTest.java | 3 +- .../facebook/FacebookPublishingHelper.java | 3 - .../publishing/flickr/FlickrChannelType.java | 183 ++++++++++++++++++ .../flickr/FlickrPublishAction.java | 137 +++++++++++++ .../flickr/FlickrPublishingHelper.java | 69 +++++++ .../flickr/FlickrPublishingModel.java | 38 ++++ .../repo/publishing/flickr/FlickrTest.java | 158 +++++++++++++++ .../flickr/springsocial/api/Flickr.java | 36 ++++ .../flickr/springsocial/api/GraphApi.java | 121 ++++++++++++ .../springsocial/api/MediaOperations.java | 42 ++++ .../springsocial/api/PhotoMetadata.java | 14 ++ .../springsocial/api/UserOperations.java | 23 +++ .../api/impl/AbstractFlickrOperations.java | 34 ++++ .../api/impl/FlickrErrorHandler.java | 144 ++++++++++++++ .../springsocial/api/impl/FlickrTemplate.java | 114 +++++++++++ .../springsocial/api/impl/MediaTemplate.java | 61 ++++++ .../api/impl/PhotoMetadataImpl.java | 29 +++ .../springsocial/api/impl/UserTemplate.java | 27 +++ .../springsocial/connect/FlickrAdapter.java | 60 ++++++ .../connect/FlickrConnectionFactory.java | 28 +++ .../connect/FlickrServiceProvider.java | 36 ++++ .../springsocial/connect/package-info.java | 4 + .../slideshare/SlideShareChannelType.java | 33 +--- .../slideshare/SlideSharePublishAction.java | 38 ++-- .../SlideSharePublishingHelper.java | 33 ++++ .../urlshortening/BitlyUrlShortenerImpl.java | 2 +- .../publishing/channels/ChannelService.java | 7 +- 34 files changed, 1520 insertions(+), 61 deletions(-) create mode 100644 config/alfresco/flickr-publishing-context.xml create mode 100644 config/alfresco/model/flickrPublishingModel.xml create mode 100644 config/test/alfresco/TestImageFile.png create mode 100644 source/java/org/alfresco/repo/publishing/flickr/FlickrChannelType.java create mode 100644 source/java/org/alfresco/repo/publishing/flickr/FlickrPublishAction.java create mode 100644 source/java/org/alfresco/repo/publishing/flickr/FlickrPublishingHelper.java create mode 100644 source/java/org/alfresco/repo/publishing/flickr/FlickrPublishingModel.java create mode 100644 source/java/org/alfresco/repo/publishing/flickr/FlickrTest.java create mode 100644 source/java/org/alfresco/repo/publishing/flickr/springsocial/api/Flickr.java create mode 100644 source/java/org/alfresco/repo/publishing/flickr/springsocial/api/GraphApi.java create mode 100644 source/java/org/alfresco/repo/publishing/flickr/springsocial/api/MediaOperations.java create mode 100644 source/java/org/alfresco/repo/publishing/flickr/springsocial/api/PhotoMetadata.java create mode 100644 source/java/org/alfresco/repo/publishing/flickr/springsocial/api/UserOperations.java create mode 100644 source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/AbstractFlickrOperations.java create mode 100644 source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/FlickrErrorHandler.java create mode 100644 source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/FlickrTemplate.java create mode 100644 source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/MediaTemplate.java create mode 100644 source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/PhotoMetadataImpl.java create mode 100644 source/java/org/alfresco/repo/publishing/flickr/springsocial/api/impl/UserTemplate.java create mode 100644 source/java/org/alfresco/repo/publishing/flickr/springsocial/connect/FlickrAdapter.java create mode 100644 source/java/org/alfresco/repo/publishing/flickr/springsocial/connect/FlickrConnectionFactory.java create mode 100644 source/java/org/alfresco/repo/publishing/flickr/springsocial/connect/FlickrServiceProvider.java create mode 100644 source/java/org/alfresco/repo/publishing/flickr/springsocial/connect/package-info.java 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 0000000000000000000000000000000000000000..ba09d11aecd69528792440102fa42193e506ba7c GIT binary patch literal 84949 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 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