diff --git a/config/alfresco/application-context-highlevel.xml b/config/alfresco/application-context-highlevel.xml
index ab59a407df..1db82bb854 100644
--- a/config/alfresco/application-context-highlevel.xml
+++ b/config/alfresco/application-context-highlevel.xml
@@ -14,6 +14,7 @@
+
diff --git a/config/alfresco/quickshare-services-context.xml b/config/alfresco/quickshare-services-context.xml
new file mode 100644
index 0000000000..5a76085d29
--- /dev/null
+++ b/config/alfresco/quickshare-services-context.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ canDownloadAnonymously
+ getMetaData
+ getTenantNodeRefFromSharedId
+
+
+
+
+
+
+
+
+
+
+ shareContent
+ update
+ unshareContent
+
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.quickshare.QuickShareService
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/source/java/org/alfresco/repo/quickshare/QuickShareServiceImpl.java b/source/java/org/alfresco/repo/quickshare/QuickShareServiceImpl.java
new file mode 100644
index 0000000000..ebdd93e380
--- /dev/null
+++ b/source/java/org/alfresco/repo/quickshare/QuickShareServiceImpl.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2005-2012 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.quickshare;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.model.QuickShareModel;
+import org.alfresco.repo.copy.CopyBehaviourCallback;
+import org.alfresco.repo.copy.CopyDetails;
+import org.alfresco.repo.copy.CopyServicePolicies;
+import org.alfresco.repo.copy.DoNothingCopyBehaviourCallback;
+import org.alfresco.repo.node.NodeServicePolicies;
+import org.alfresco.repo.policy.JavaBehaviour;
+import org.alfresco.repo.policy.PolicyComponent;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
+import org.alfresco.repo.tenant.TenantService;
+import org.alfresco.repo.tenant.TenantUtil;
+import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork;
+import org.alfresco.repo.thumbnail.ThumbnailDefinition;
+import org.alfresco.service.cmr.attributes.AttributeService;
+import org.alfresco.service.cmr.quickshare.InvalidSharedIdException;
+import org.alfresco.service.cmr.quickshare.QuickShareDTO;
+import org.alfresco.service.cmr.quickshare.QuickShareDisabledException;
+import org.alfresco.service.cmr.quickshare.QuickShareService;
+import org.alfresco.service.cmr.repository.ContentData;
+import org.alfresco.service.cmr.repository.InvalidNodeRefException;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.security.NoSuchPersonException;
+import org.alfresco.service.cmr.security.PersonService;
+import org.alfresco.service.cmr.thumbnail.ThumbnailService;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.util.EqualsHelper;
+import org.alfresco.util.Pair;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.safehaus.uuid.UUID;
+import org.safehaus.uuid.UUIDGenerator;
+
+/**
+ * QuickShare Service implementation.
+ *
+ * In addition to the quick share service, this class also provides a BeforeDeleteNodePolicy and
+ * OnCopyNodePolicy for content with the QuickShare aspect.
+ *
+ * @author Alex Miller, janv
+ */
+public class QuickShareServiceImpl implements QuickShareService, NodeServicePolicies.BeforeDeleteNodePolicy, CopyServicePolicies.OnCopyNodePolicy
+{
+ private static final Log logger = LogFactory.getLog(QuickShareServiceImpl.class);
+
+ static final String ATTR_KEY_SHAREDIDS_ROOT = ".sharedIds";
+
+
+ private boolean enabled;
+
+ private AttributeService attributeService;
+ private NodeService nodeService;
+ private PersonService personService;
+ private PolicyComponent policyComponent;
+ private TenantService tenantService;
+ private ThumbnailService thumbnailService;
+
+ /**
+ * Enable or disable this service.
+ */
+ public void setEnabled(boolean enabled)
+ {
+ this.enabled = enabled;
+ }
+
+ /**
+ * Set the attribute service
+ */
+ public void setAttributeService(AttributeService attributeService)
+ {
+ this.attributeService = attributeService;
+ }
+
+ /**
+ * Set the node service
+ */
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+
+ /**
+ * Set the person service
+ */
+ public void setPersonService(PersonService personService)
+ {
+ this.personService = personService;
+ }
+
+ /**
+ * Set the policy component
+ */
+ public void setPolicyComponent(PolicyComponent policyComponent)
+ {
+ this.policyComponent = policyComponent;
+ }
+
+ /**
+ * Set the tenant service
+ */
+ public void setTenantService(TenantService tenantService)
+ {
+ this.tenantService = tenantService;
+ }
+
+ /**
+ * Set the thumbnail service
+ */
+ public void setThumbnailService(ThumbnailService thumbnailService)
+ {
+ this.thumbnailService = thumbnailService;
+ }
+
+ /**
+ * The initialise method. Register our policies.
+ */
+ public void init()
+ {
+ // Register interest in the beforeDeleteNode policy - note: currently for content only !!
+ policyComponent.bindClassBehaviour(
+ QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"),
+ ContentModel.TYPE_CONTENT,
+ new JavaBehaviour(this, "beforeDeleteNode"));
+
+ //Register interest in the onCopyNodePolicy to block copying of quick share metadta
+ policyComponent.bindClassBehaviour(
+ CopyServicePolicies.OnCopyNodePolicy.QNAME,
+ QuickShareModel.ASPECT_QSHARE,
+ new JavaBehaviour(this, "getCopyCallback"));
+ }
+
+
+ @Override
+ public QuickShareDTO shareContent(NodeRef nodeRef)
+ {
+ checkEnabled();
+
+ //Check the node is the correct type
+ QName typeQName = nodeService.getType(nodeRef);
+ if (! typeQName.equals(ContentModel.TYPE_CONTENT))
+ {
+ throw new InvalidNodeRefException(nodeRef);
+ }
+
+ final String sharedId;
+
+ // Only add the quick share aspect if it isn't already present.
+ // If it is retura dto built from the existing properties.
+ if (! nodeService.getAspects(nodeRef).contains(QuickShareModel.ASPECT_QSHARE))
+ {
+ UUID uuid = UUIDGenerator.getInstance().generateRandomBasedUUID();
+ sharedId = Base64.encodeBase64URLSafeString(uuid.toByteArray()); // => 22 chars (eg. q3bEKPeDQvmJYgt4hJxOjw)
+
+ Map props = new HashMap(2);
+ props.put(QuickShareModel.PROP_QSHARE_SHAREDID, sharedId);
+ props.put(QuickShareModel.PROP_QSHARE_SHAREDBY, AuthenticationUtil.getRunAsUser());
+
+ nodeService.addAspect(nodeRef, QuickShareModel.ASPECT_QSHARE, props);
+
+ final NodeRef tenantNodeRef = tenantService.getName(nodeRef);
+
+ TenantUtil.runAsDefaultTenant(new TenantRunAsWork()
+ {
+ public Void doWork() throws Exception
+ {
+ attributeService.setAttribute(tenantNodeRef, ATTR_KEY_SHAREDIDS_ROOT, sharedId);
+ return null;
+ }
+ });
+
+ if (logger.isInfoEnabled())
+ {
+ logger.info("QuickShare - shared content: "+sharedId+" ["+nodeRef+"]");
+ }
+ }
+ else
+ {
+ sharedId = (String)nodeService.getProperty(nodeRef, QuickShareModel.PROP_QSHARE_SHAREDID);
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("QuickShare - content already shared: "+sharedId+" ["+nodeRef+"]");
+ }
+ }
+
+
+ return new QuickShareDTO(sharedId);
+ }
+
+ /**
+ * Is this service enable?
+ * @throws uickShareDisabledException if it isn't.
+ */
+ private void checkEnabled()
+ {
+ if (enabled == false)
+ {
+ throw new QuickShareDisabledException("QuickShare is disabled system-wide");
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Map getMetaData(NodeRef nodeRef)
+ {
+ checkEnabled();
+
+ Map nodeProps = getNodeProperties(nodeRef);
+ ContentData contentData = (ContentData)nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT);
+
+ String modifierUserName = (String)nodeProps.get(ContentModel.PROP_MODIFIER);
+ Map personProps = null;
+ if (modifierUserName != null)
+ {
+ try
+ {
+ NodeRef personRef = personService.getPerson(modifierUserName);
+ if (personRef != null)
+ {
+ personProps = nodeService.getProperties(personRef);
+ }
+ }
+ catch (NoSuchPersonException nspe)
+ {
+ // absorb this exception - eg. System (or maybe the user has been deleted)
+ if (logger.isInfoEnabled())
+ {
+ logger.info("MetaDataGet - no such person: "+modifierUserName);
+ }
+ }
+ }
+
+ Map metadata = new HashMap(8);
+
+ metadata.put("name", nodeProps.get(ContentModel.PROP_NAME));
+ metadata.put("title", nodeProps.get(ContentModel.PROP_TITLE));
+
+ if (contentData != null)
+ {
+ metadata.put("mimetype", contentData.getMimetype());
+ metadata.put("size", contentData.getSize());
+ }
+ else
+ {
+ metadata.put("size", 0L);
+ }
+
+ metadata.put("modified", nodeProps.get(ContentModel.PROP_MODIFIED));
+
+ if (personProps != null)
+ {
+ metadata.put("modifierFirstName", personProps.get(ContentModel.PROP_FIRSTNAME));
+ metadata.put("modifierLastName", personProps.get(ContentModel.PROP_LASTNAME));
+ }
+
+ // thumbnail defs for this nodeRef
+ List thumbnailDefs = new ArrayList(7);
+ if (contentData != null)
+ {
+ // Note: thumbnail defs only appear in this list if they can produce a thumbnail for the content
+ // found in the content property of this node. This will be determined by looking at the mimetype of the content
+ // and the destination mimetype of the thumbnail.
+ List thumbnailDefinitions = thumbnailService.getThumbnailRegistry().getThumbnailDefinitions(contentData.getMimetype(), contentData.getSize());
+ for (ThumbnailDefinition thumbnailDefinition : thumbnailDefinitions)
+ {
+ thumbnailDefs.add(thumbnailDefinition.getName());
+ }
+ }
+ metadata.put("thumbnailDefinitions", thumbnailDefs);
+
+ // thumbnail instances for this nodeRef
+ List thumbnailRefs = thumbnailService.getThumbnails(nodeRef, ContentModel.PROP_CONTENT, null, null);
+ List thumbnailNames = new ArrayList(thumbnailRefs.size());
+ for (NodeRef thumbnailRef : thumbnailRefs)
+ {
+ thumbnailNames.add((String)nodeService.getProperty(thumbnailRef, ContentModel.PROP_NAME));
+ }
+ metadata.put("thumbnailNames", thumbnailNames);
+
+ metadata.put("lastThumbnailModificationData", (List)nodeProps.get(ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA));
+
+ if (nodeProps.containsKey(QuickShareModel.PROP_QSHARE_SHAREDID))
+ {
+ metadata.put("sharedId", nodeProps.get(QuickShareModel.PROP_QSHARE_SHAREDID));
+ }
+
+ Map model = new HashMap(1);
+ model.put("item", metadata);
+ return model;
+ }
+
+ private Map getNodeProperties(NodeRef nodeRef)
+ {
+ QName typeQName = nodeService.getType(nodeRef);
+ if (! typeQName.equals(ContentModel.TYPE_CONTENT))
+ {
+ throw new InvalidNodeRefException(nodeRef);
+ }
+
+ Map nodeProps = nodeService.getProperties(nodeRef);
+ return nodeProps;
+ }
+
+ @Override
+ public Pair getTenantNodeRefFromSharedId(final String sharedId)
+ {
+ final NodeRef nodeRef = TenantUtil.runAsDefaultTenant(new TenantRunAsWork()
+ {
+ public NodeRef doWork() throws Exception
+ {
+ return (NodeRef)attributeService.getAttribute(ATTR_KEY_SHAREDIDS_ROOT, sharedId);
+ }
+ });
+
+ if (nodeRef == null)
+ {
+ throw new InvalidSharedIdException(sharedId);
+ }
+
+ // note: relies on tenant-specific (ie. mangled) nodeRef
+ String tenantDomain = tenantService.getDomain(nodeRef.getStoreRef().getIdentifier());
+
+ return new Pair(tenantDomain, tenantService.getBaseName(nodeRef));
+ }
+
+ @Override
+ public Map getMetaData(String sharedId)
+ {
+ Pair pair = getTenantNodeRefFromSharedId(sharedId);
+ final String tenantDomain = pair.getFirst();
+ final NodeRef nodeRef = pair.getSecond();
+
+ Map model = TenantUtil.runAsSystemTenant(new TenantRunAsWork