/*
* 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.googledocs;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.MimetypeService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.security.AccessPermission;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.service.cmr.security.OwnableService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.ParameterCheck;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.google.gdata.client.GoogleService;
import com.google.gdata.client.GoogleAuthTokenFactory.UserToken;
import com.google.gdata.client.docs.DocsService;
import com.google.gdata.data.IEntry;
import com.google.gdata.data.MediaContent;
import com.google.gdata.data.PlainTextConstruct;
import com.google.gdata.data.acl.AclEntry;
import com.google.gdata.data.acl.AclFeed;
import com.google.gdata.data.acl.AclRole;
import com.google.gdata.data.acl.AclScope;
import com.google.gdata.data.docs.DocumentEntry;
import com.google.gdata.data.docs.DocumentListEntry;
import com.google.gdata.data.docs.FolderEntry;
import com.google.gdata.data.docs.PdfEntry;
import com.google.gdata.data.docs.PresentationEntry;
import com.google.gdata.data.docs.SpreadsheetEntry;
import com.google.gdata.data.docs.DocumentListEntry.MediaType;
import com.google.gdata.data.media.MediaSource;
import com.google.gdata.data.media.MediaStreamSource;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ContentType;
import com.google.gdata.util.ServiceException;
/**
* Google docs integration service implementation
*/
public class GoogleDocsServiceImpl extends TransactionListenerAdapter
implements GoogleDocsService, GoogleDocsModel
{
/** Log */
private static Log logger = LogFactory.getLog(GoogleDocsServiceImpl.class);
/** Google document types */
public static final String TYPE_DOCUMENT = "document";
public static final String TYPE_SPREADSHEET = "spreadsheet";
public static final String TYPE_PRESENTATION = "presentation";
public static final String TYPE_PDF = "pdf";
/** Transaction resource keys */
private final static String KEY_MARKED_CREATE = "google_doc_service.marked_resources";
private final static String KEY_MARKED_DELETE = "google_doc_service.marked_delete";
/** Services */
private DocsService googleDocumentService;
private GoogleService spreadsheetsService;
private NodeService nodeService;
private ContentService contentService;
private PersonService personService;
private MimetypeService mimetypeService;
private PermissionService permissionService;
private OwnableService ownableService;
private AuthorityService authorityService;
private DictionaryService dictionaryService;
/** GoogleDoc base feed url */
private String url = "http://docs.google.com/feeds/default/private/full";
private String downloadUrl = "https://docs.google.com/feeds/download";
/** Authentication credentials */
private boolean initialised = false;
private String username;
private String password;
/** Permission map */
private Map permissionMap;
/**
* @param googleDocumentService google document service
*/
public void setGoogleDocumentService(DocsService googleDocumentService)
{
this.googleDocumentService = googleDocumentService;
}
/**
* @param spreadsheetsService spread sheets service
*/
public void setSpreadsheetsService(GoogleService spreadsheetsService)
{
this.spreadsheetsService = spreadsheetsService;
}
/**
* @param nodeService node service
*/
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
/**
* @param contentService content service
*/
public void setContentService(ContentService contentService)
{
this.contentService = contentService;
}
/**
* @param personService person service
*/
public void setPersonService(PersonService personService)
{
this.personService = personService;
}
/**
* @param mimetypeService mime type service
*/
public void setMimetypeService(MimetypeService mimetypeService)
{
this.mimetypeService = mimetypeService;
}
/**
* @param permissionService permission service
*/
public void setPermissionService(PermissionService permissionService)
{
this.permissionService = permissionService;
}
/**
* @param ownableService ownable service
*/
public void setOwnableService(OwnableService ownableService)
{
this.ownableService = ownableService;
}
/**
* @param authorityService authority service
*/
public void setAuthorityService(AuthorityService authorityService)
{
this.authorityService = authorityService;
}
/**
* @param dictionaryService dictionary service
*/
public void setDictionaryService(DictionaryService dictionaryService)
{
this.dictionaryService = dictionaryService;
}
/**
* @param url root googleDoc URL
*/
public void setUrl(String url)
{
this.url = url;
}
/**
* @param downloadUrl root download URL
*/
public void setDownloadUrl(String downloadUrl)
{
this.downloadUrl = downloadUrl;
}
/**
* @param username google service user name
*/
public void setUsername(String username)
{
this.username = username;
}
/**
* @param password google service password
*/
public void setPassword(String password)
{
this.password = password;
}
/**
* @param permissionMap permission map
*/
public void setPermissionMap(Map permissionMap)
{
this.permissionMap = permissionMap;
}
/**
* Initialise google docs services
*/
public void initialise() throws GoogleDocsServiceInitException
{
if (initialised == false)
{
if (logger.isDebugEnabled() == true)
{
logger.debug("Trying to initialise google docs service for user " + username);
}
if (username == null ||username.length() == 0 || password == null)
{
throw new GoogleDocsServiceInitException("No Goolge Docs credentials found. Please set the Google Docs authentication configuration.");
}
try
{
googleDocumentService.setUserCredentials(username, password);
spreadsheetsService.setUserCredentials(username, password);
googleDocumentService.setChunkedMediaUpload(-1);
}
catch (AuthenticationException e)
{
throw new GoogleDocsServiceInitException("Unable to connect to Google Docs. Please check the Google Docs authentication configuration.", e);
}
initialised = true;
if (logger.isDebugEnabled() == true)
{
logger.debug("Successfully initialised google docs service for user " + username);
}
}
}
/**
* @see org.alfresco.google.docs.GoogleDocsService#upload(org.alfresco.service.cmr.repository.NodeRef)
*/
public void createGoogleDoc(NodeRef nodeRef, GoogleDocsPermissionContext permissionContext)
{
// Check for mandatory parameters
ParameterCheck.mandatory("nodeRef", nodeRef);
// Initialise google doc services
try
{
initialise();
}
catch (GoogleDocsServiceInitException e)
{
throw new AlfrescoRuntimeException("Unable to create google doc, because service could not be initialised.", e);
}
// Get property values
String name = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
// TODO should be checking to make sure this doesn't already have an associated google doc
// Get content reader
String mimetype = null;
InputStream is = null;
ContentReader contentReader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
if (contentReader == null)
{
// Determine the mimetype from the file extension
mimetype = mimetypeService.guessMimetype(name);
}
else
{
// Get the mime type and input stream from the content reader
mimetype = contentReader.getMimetype();
if (contentReader.getSize() != 0)
{
is = contentReader.getContentInputStream();
}
}
// Get the parent folder id
DocumentListEntry parentFolder = getParentFolder(nodeRef);
if (logger.isDebugEnabled() == true)
{
logger.debug("Creating google document (" + name + "," + mimetype + ")");
}
// Create the new google document
DocumentListEntry document = createGoogleDocument(name, mimetype, parentFolder, is);
// Set permissions
setGoogleResourcePermissions(nodeRef, document, permissionContext);
// Set the google document details
setResourceDetails(nodeRef, document);
}
/**
* @see org.alfresco.google.docs.GoogleDocsService#deleteGoogleResource(org.alfresco.service.cmr.repository.NodeRef)
*/
public void deleteGoogleResource(NodeRef nodeRef)
{
// Check for mandatory parameters
ParameterCheck.mandatory("nodeRef", nodeRef);
// Initialise google doc services
try
{
initialise();
}
catch (GoogleDocsServiceInitException e)
{
throw new AlfrescoRuntimeException("Unable to create google doc, because service could not be initialised.", e);
}
if (nodeService.hasAspect(nodeRef, ASPECT_GOOGLERESOURCE) == true)
{
// Get the entry
DocumentListEntry entry = getDocumentListEntry(nodeRef);
if (entry != null)
{
// Mark the resource for deletion upon completion of the transaction
markResource(KEY_MARKED_DELETE, entry.getResourceId());
}
// Remove the aspect from the node
nodeService.removeAspect(nodeRef, ASPECT_GOOGLERESOURCE);
}
}
/**
* Set a google permission on a specified resource
*
* @param nodeRef node reference
* @param resource document resource
* @param permissionContext permission context
*/
private void setGoogleResourcePermissions(NodeRef nodeRef, DocumentListEntry resource, GoogleDocsPermissionContext permissionContext)
{
if (GoogleDocsPermissionContext.PRIVATE.equals(permissionContext) == false)
{
Set accessPermissions = permissionService.getAllSetPermissions(nodeRef);
for (AccessPermission accessPermission : accessPermissions)
{
String authorityName = accessPermission.getAuthority();
AuthorityType authorityType = accessPermission.getAuthorityType();
String permission = accessPermission.getPermission();
if (permissionMap.containsKey(permission) == true)
{
String aclRole = permissionMap.get(permission);
if (GoogleDocsPermissionContext.SHARE_READ.equals(permissionContext) == true &&
("reader".equals(aclRole) == true || "writer".equals(aclRole) == true))
{
// Set the permission to read
setGoogleResourcePermission(resource, authorityType, authorityName, "reader");
}
else if (GoogleDocsPermissionContext.SHARE_WRITE.equals(permissionContext) == true &&
"writer".equals(aclRole) == true)
{
// Set the permission to write
setGoogleResourcePermission(resource, authorityType, authorityName, "writer");
}
else if (GoogleDocsPermissionContext.SHARE_READWRITE.equals(permissionContext) == true &&
("reader".equals(aclRole) == true || "writer".equals(aclRole) == true))
{
// Set the permission to the current acl
setGoogleResourcePermission(resource, authorityType, authorityName, aclRole);
}
}
}
}
}
/**
* Set a google permission on a specified resource
*
* @param resource document resource
* @param authorityType authority type
* @param authorityName authority name
* @param role role
*/
private void setGoogleResourcePermission(DocumentListEntry resource, AuthorityType authorityType, String authorityName, String role)
{
if (AuthorityType.USER.equals(authorityType) == true)
{
// Set the user permissions on the resource
String userEMail = getUserEMail(authorityName);
if (userEMail != null && userEMail.length() != 0)
{
setGoogleResourcePermission(resource, userEMail, role);
}
}
else if (AuthorityType.GROUP.equals(authorityType) == true)
{
Set childAuthorities = authorityService.getContainedAuthorities(AuthorityType.USER, authorityName, false);
for (String childAuthority : childAuthorities)
{
setGoogleResourcePermission(resource, AuthorityType.USER, childAuthority, role);
}
}
}
/**
* Gets the users email used to identify their google account.
*
* @param userName user name
* @return String google account email, null if none
*/
private String getUserEMail(String userName)
{
String email = null;
NodeRef personNodeRef = personService.getPerson(userName);
if (personNodeRef != null)
{
// First see if the google user information has been set
email = (String)nodeService.getProperty(personNodeRef, ContentModel.PROP_GOOGLEUSERNAME);
// If no google user information then default back to the user's email
if (email == null || email.length() == 0)
{
email = (String) nodeService.getProperty(personNodeRef, ContentModel.PROP_EMAIL);
}
}
return email;
}
/**
* Gets the nodes parent folder google resource.
*
* @param nodeRef node reference
* @return DocumentList Entry folder resource
*/
private DocumentListEntry getParentFolder(final NodeRef nodeRef)
{
DocumentListEntry folder = null;
NodeRef parentNodeRef = nodeService.getPrimaryParent(nodeRef).getParentRef();
if (parentNodeRef != null)
{
if (nodeService.hasAspect(parentNodeRef, ASPECT_GOOGLERESOURCE) == true)
{
String resourceType = (String)nodeService.getProperty(parentNodeRef, PROP_RESOURCE_TYPE);
String resourceId = (String)nodeService.getProperty(parentNodeRef, PROP_RESOURCE_ID);
folder = getDocumentListEntry(resourceType + ":" + resourceId);
if (logger.isDebugEnabled() == true)
{
logger.debug("Found existing google folder + " + resourceId);
}
}
else
{
// Get the parent folder
DocumentListEntry parentFolder = getParentFolder(parentNodeRef);
if (parentFolder != null)
{
// Determine the name of the new google folder
String name = null;
QName parentNodeType = nodeService.getType(parentNodeRef);
if (dictionaryService.isSubClass(parentNodeType, ContentModel.TYPE_STOREROOT) == true)
{
name = parentNodeRef.getStoreRef().getIdentifier();
}
else
{
name = (String)nodeService.getProperty(parentNodeRef, ContentModel.PROP_NAME);
}
// Create the folder and set the meta data in Alfresco
folder = createGoogleFolder(name, parentFolder);
setResourceDetails(parentNodeRef, folder);
// Set the owner of the document
setGoogleResourcePermission(folder, AuthorityType.USER, username, "owner");
// Set the owner of the document
setGoogleResourcePermission(folder, AuthorityType.USER, username, "owner");
}
}
}
return folder;
}
/**
* Sets the resource details on the node reference
*
* @param nodeRef node reference
* @param documentListEntry document list entry
*/
private void setResourceDetails(final NodeRef nodeRef, final DocumentListEntry documentListEntry)
{
AuthenticationUtil.RunAsWork