Merge branch 'feature/REPO-1851_get_avatar' into 'develop'

Avatars: retrieve, update and delete

See merge request !23
This commit is contained in:
Alex Mukha
2017-08-23 11:25:47 +01:00
parent 3550c00a32
commit 6fed14733c
10 changed files with 794 additions and 71 deletions

View File

@@ -25,15 +25,18 @@
*/
package org.alfresco.rest.api;
import java.io.InputStream;
import java.util.List;
import org.alfresco.rest.api.model.PasswordReset;
import org.alfresco.rest.api.model.Person;
import org.alfresco.rest.framework.resource.content.BasicContentInfo;
import org.alfresco.rest.framework.resource.content.BinaryResource;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.security.NoSuchPersonException;
import java.util.List;
public interface People
{
String DEFAULT_USER = "-me-";
@@ -102,4 +105,28 @@ public interface People
* @param passwordReset the password reset details
*/
void resetPassword(String personId, PasswordReset passwordReset);
/**
*
* @param personId
* @param parameters
* @return
*/
BinaryResource downloadAvatarContent(String personId, Parameters parameters);
/**
*
* @param personId
* @param contentInfo
* @param stream
* @param parameters
* @return
*/
Person uploadAvatarContent(String personId, BasicContentInfo contentInfo, InputStream stream, Parameters parameters);
/**
*
* @param personId
*/
void deleteAvatarContent(String personId);
}

View File

@@ -70,6 +70,16 @@ public interface Renditions
*/
void createRendition(String nodeId, Rendition rendition, Parameters parameters);
/**
* Creates a rendition for the given node - either async r sync
*
* @param nodeId
* @param rendition
* @param executeAsync
* @param parameters
*/
void createRendition(String nodeId, Rendition rendition, boolean executeAsync, Parameters parameters);
/**
* Downloads rendition.
*

View File

@@ -37,18 +37,25 @@ import org.alfresco.repo.security.authentication.ResetPasswordServiceImpl.ResetP
import org.alfresco.repo.security.authentication.ResetPasswordServiceImpl.ResetPasswordWorkflowInvalidUserException;
import org.alfresco.rest.api.Nodes;
import org.alfresco.rest.api.People;
import org.alfresco.rest.api.Renditions;
import org.alfresco.rest.api.Sites;
import org.alfresco.rest.api.model.Node;
import org.alfresco.rest.api.model.PasswordReset;
import org.alfresco.rest.api.model.Person;
import org.alfresco.rest.api.model.Rendition;
import org.alfresco.rest.framework.core.exceptions.ConstraintViolatedException;
import org.alfresco.rest.framework.core.exceptions.DisabledServiceException;
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
import org.alfresco.rest.framework.resource.content.BasicContentInfo;
import org.alfresco.rest.framework.resource.content.BinaryResource;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Paging;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.rest.framework.resource.parameters.SortColumn;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
@@ -65,6 +72,7 @@ import org.alfresco.util.Pair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.InputStream;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.ArrayList;
@@ -97,9 +105,9 @@ public class PeopleImpl implements People
PermissionService.GROUP_PREFIX,
PermissionService.ROLE_PREFIX
};
protected Nodes nodes;
protected Sites sites;
protected SiteService siteService;
protected NodeService nodeService;
protected PersonService personService;
@@ -109,6 +117,7 @@ public class PeopleImpl implements People
protected ContentService contentService;
protected ThumbnailService thumbnailService;
protected ResetPasswordService resetPasswordService;
protected Renditions renditions;
private final static Map<String, QName> sort_params_to_qnames;
static
@@ -175,6 +184,12 @@ public class PeopleImpl implements People
this.resetPasswordService = resetPasswordService;
}
public void setRenditions(Renditions renditions)
{
this.renditions = renditions;
}
/**
* Validate, perform -me- substitution and canonicalize the person ID.
*
@@ -257,46 +272,22 @@ public class PeopleImpl implements People
public boolean hasAvatar(NodeRef personNodeRef)
{
if(personNodeRef != null)
{
List<AssociationRef> avatorAssocs = nodeService.getTargetAssocs(personNodeRef, ContentModel.ASSOC_AVATAR);
return(avatorAssocs.size() > 0);
}
else
{
return false;
}
return (getAvatarOriginal(personNodeRef) != null);
}
@Override
public NodeRef getAvatar(String personId)
{
NodeRef avatar = null;
personId = validatePerson(personId);
NodeRef personNode = personService.getPerson(personId);
if(personNode != null)
{
List<AssociationRef> avatorAssocs = nodeService.getTargetAssocs(personNode, ContentModel.ASSOC_AVATAR);
if(avatorAssocs.size() > 0)
{
AssociationRef ref = avatorAssocs.get(0);
NodeRef thumbnailNodeRef = thumbnailService.getThumbnailByName(ref.getTargetRef(), ContentModel.PROP_CONTENT, "avatar");
if(thumbnailNodeRef != null)
{
avatar = thumbnailNodeRef;
}
else
{
throw new EntityNotFoundException("avatar");
}
}
else
{
throw new EntityNotFoundException("avatar");
}
NodeRef avatarOrig = getAvatarOriginal(personNode);
avatar = thumbnailService.getThumbnailByName(avatarOrig, ContentModel.PROP_CONTENT, "avatar");
}
else
if (avatar == null)
{
throw new EntityNotFoundException(personId);
}
@@ -304,6 +295,111 @@ public class PeopleImpl implements People
return avatar;
}
private NodeRef getAvatarOriginal(NodeRef personNode)
{
NodeRef avatarOrigNodeRef = null;
List<ChildAssociationRef> avatarChildAssocs = nodeService.getChildAssocs(personNode, Collections.singleton(ContentModel.ASSOC_PREFERENCE_IMAGE));
if (avatarChildAssocs.size() > 0)
{
ChildAssociationRef ref = avatarChildAssocs.get(0);
avatarOrigNodeRef = ref.getChildRef();
}
else
{
// TODO do we still need this ? - backward compatible with JSF web-client avatar
List<AssociationRef> avatorAssocs = nodeService.getTargetAssocs(personNode, ContentModel.ASSOC_AVATAR);
if (avatorAssocs.size() > 0)
{
AssociationRef ref = avatorAssocs.get(0);
avatarOrigNodeRef = ref.getTargetRef();
}
}
return avatarOrigNodeRef;
}
@Override
public BinaryResource downloadAvatarContent(String personId, Parameters parameters)
{
personId = validatePerson(personId);
NodeRef personNode = personService.getPerson(personId);
NodeRef avatarNodeRef = getAvatarOriginal(personNode);
return renditions.getContent(avatarNodeRef, "avatar", parameters);
}
@Override
public Person uploadAvatarContent(String personId, BasicContentInfo contentInfo, InputStream stream, Parameters parameters)
{
if (!thumbnailService.getThumbnailsEnabled())
{
throw new DisabledServiceException("Thumbnail generation has been disabled.");
}
personId = validatePerson(personId);
checkCurrentUserOrAdmin(personId);
NodeRef personNode = personService.getPerson(personId);
NodeRef avatarOrigNodeRef = getAvatarOriginal(personNode);
if (avatarOrigNodeRef != null)
{
deleteAvatar(avatarOrigNodeRef);
}
QName origAvatarQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "origAvatar");
nodeService.addAspect(personNode, ContentModel.ASPECT_PREFERENCES, null);
ChildAssociationRef assoc = nodeService.createNode(personNode, ContentModel.ASSOC_PREFERENCE_IMAGE, origAvatarQName,
ContentModel.TYPE_CONTENT);
NodeRef avatar = assoc.getChildRef();
String avatarOriginalNodeId = avatar.getId();
// TODO do we still need this ? - backward compatible with JSF web-client avatar
nodeService.createAssociation(personNode, avatar, ContentModel.ASSOC_AVATAR);
Node n = nodes.updateContent(avatarOriginalNodeId, contentInfo, stream, parameters);
String mimeType = n.getContent().getMimeType();
if (mimeType.indexOf("image/") != 0)
{
throw new InvalidArgumentException(
"Uploaded content must be an image (content type determined to be '"+mimeType+"')");
}
// create thumbnail synchronously
Rendition avatarR = new Rendition();
avatarR.setId("avatar");
renditions.createRendition(avatarOriginalNodeId, avatarR, false, parameters);
List<String> include = Arrays.asList(
PARAM_INCLUDE_ASPECTNAMES,
PARAM_INCLUDE_PROPERTIES);
return getPersonWithProperties(personId, include);
}
@Override
public void deleteAvatarContent(String personId)
{
personId = validatePerson(personId);
checkCurrentUserOrAdmin(personId);
NodeRef personNode = personService.getPerson(personId);
NodeRef avatarOrigNodeRef = getAvatarOriginal(personNode);
if (avatarOrigNodeRef != null)
{
deleteAvatar(avatarOrigNodeRef);
}
}
private void deleteAvatar(NodeRef avatarOrigNodeRef)
{
// Set as temporary to permanently delete node (instead of archiving)
nodeService.addAspect(avatarOrigNodeRef, ContentModel.ASPECT_TEMPORARY, null);
nodeService.deleteNode(avatarOrigNodeRef);
}
/**
* Get a full representation of a person.
*
@@ -612,14 +708,8 @@ public class PeopleImpl implements People
personId = validatePerson(personId);
validateUpdatePersonData(person);
boolean isAdmin = isAdminAuthority();
String currentUserId = AuthenticationUtil.getFullyAuthenticatedUser();
if (!isAdmin && !currentUserId.equalsIgnoreCase(personId))
{
// The user is not an admin user and is not attempting to update *their own* details.
throw new PermissionDeniedException();
}
// Check if user updating *their own* details or is an admin
boolean isAdmin = checkCurrentUserOrAdmin(personId);
final String personIdToUpdate = validatePerson(personId);
final Map<QName, Serializable> properties = person.toProperties();
@@ -675,6 +765,19 @@ public class PeopleImpl implements People
return getPerson(personId);
}
private boolean checkCurrentUserOrAdmin(String personId)
{
boolean isAdmin = isAdminAuthority();
String currentUserId = AuthenticationUtil.getFullyAuthenticatedUser();
if (!isAdmin && !currentUserId.equalsIgnoreCase(personId))
{
throw new PermissionDeniedException();
}
return isAdmin;
}
private void validateUpdatePersonData(Person person)
{
validateNamespaces(person.getAspectNames(), person.getProperties());

View File

@@ -260,6 +260,12 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware
@Override
public void createRendition(String nodeId, Rendition rendition, Parameters parameters)
{
createRendition(nodeId, rendition, true, parameters);
}
@Override
public void createRendition(String nodeId, Rendition rendition, boolean executeAsync, Parameters parameters)
{
// If thumbnail generation has been configured off, then don't bother.
if (!thumbnailService.getThumbnailsEnabled())
@@ -292,8 +298,9 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware
}
Action action = ThumbnailHelper.createCreateThumbnailAction(thumbnailDefinition, serviceRegistry);
// Queue async creation of thumbnail
actionService.executeAction(action, sourceNodeRef, true, true);
// Create thumbnail - or else queue for async creation
actionService.executeAction(action, sourceNodeRef, true, executeAsync);
}
@Override
@@ -324,7 +331,15 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware
{
throw new NotFoundException("Thumbnail was not found for [" + renditionId + ']');
}
String sourceNodeMimeType = getMimeType(sourceNodeRef);
String sourceNodeMimeType = null;
try
{
sourceNodeMimeType = (sourceNodeRef != null ? getMimeType(sourceNodeRef) : null);
}
catch (InvalidArgumentException e)
{
// No content for node, e.g. ASSOC_AVATAR rather than ASSOC_PREFERENCE_IMAGE
}
// resource based on the content's mimeType and rendition id
String phPath = scriptThumbnailService.getMimeAwarePlaceHolderResourcePath(renditionId, sourceNodeMimeType);
if (phPath == null)
@@ -388,6 +403,11 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware
protected NodeRef getRenditionByName(NodeRef nodeRef, String renditionId, Parameters parameters)
{
if (nodeRef == null)
{
return null;
}
if (StringUtils.isEmpty(renditionId))
{
throw new InvalidArgumentException("renditionId can't be null or empty.");

View File

@@ -25,19 +25,30 @@
*/
package org.alfresco.rest.api.people;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.alfresco.model.ContentModel;
import org.alfresco.rest.api.People;
import org.alfresco.rest.api.model.Client;
import org.alfresco.rest.api.model.PasswordReset;
import org.alfresco.rest.api.model.Person;
import org.alfresco.rest.framework.BinaryProperties;
import org.alfresco.rest.framework.Operation;
import org.alfresco.rest.framework.WebApiDescription;
import org.alfresco.rest.framework.WebApiNoAuth;
import org.alfresco.rest.framework.WebApiParam;
import org.alfresco.rest.framework.core.ResourceParameter;
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.framework.resource.EntityResource;
import org.alfresco.rest.framework.resource.actions.interfaces.BinaryResourceAction;
import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction;
import org.alfresco.rest.framework.resource.content.BasicContentInfo;
import org.alfresco.rest.framework.resource.content.BinaryResource;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.rest.framework.webscripts.WithResponse;
@@ -46,10 +57,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
/**
* An implementation of an Entity Resource for a Person
*
@@ -57,7 +64,10 @@ import java.util.List;
* @author Gethin James
*/
@EntityResource(name="people", title = "People")
public class PeopleEntityResource implements EntityResourceAction.ReadById<Person>, EntityResourceAction.Create<Person>, EntityResourceAction.Update<Person>,EntityResourceAction.Read<Person>, InitializingBean
public class PeopleEntityResource implements EntityResourceAction.ReadById<Person>, EntityResourceAction.Create<Person>,
EntityResourceAction.Update<Person>,EntityResourceAction.Read<Person>,
BinaryResourceAction.Read, BinaryResourceAction.Update<Person>, BinaryResourceAction.Delete, InitializingBean
{
private static Log logger = LogFactory.getLog(PeopleEntityResource.class);
@@ -169,4 +179,54 @@ public class PeopleEntityResource implements EntityResourceAction.ReadById<Perso
{
people.resetPassword(personId, passwordReset);
}
/**
* Download avatar image content
*
* @param personId
* @param parameters {@link Parameters}
* @return
* @throws EntityNotFoundException
*/
@Override
@WebApiDescription(title = "Download avatar", description = "Download avatar")
@BinaryProperties({"avatar"})
public BinaryResource readProperty(String personId, Parameters parameters) throws EntityNotFoundException
{
return people.downloadAvatarContent(personId, parameters);
}
/**
* Upload avatar image content
*
* @param personId
* @param contentInfo Basic information about the content stream
* @param stream An inputstream
* @param parameters
* @return
*/
@Override
@WebApiDescription(title = "Upload avatar", description = "Upload avatar")
@BinaryProperties({"avatar"})
public Person updateProperty(String personId, BasicContentInfo contentInfo, InputStream stream, Parameters parameters)
{
return people.uploadAvatarContent(personId, contentInfo, stream, parameters);
}
/**
* Delete avatar image content
*
* @param personId
* @param parameters
* @return
*/
@Override
@WebApiDescription(title = "Delete avatar image", description = "Delete avatar image")
@BinaryProperties({ "avatar" })
public void deleteProperty(String personId, Parameters parameters)
{
people.deleteAvatarContent(personId);
}
}

View File

@@ -667,6 +667,7 @@
<bean id="people" class="org.alfresco.rest.api.impl.PeopleImpl">
<property name="nodes" ref="Nodes" />
<property name="sites" ref="sites" />
<property name="renditions" ref="Renditions" />
<property name="siteService" ref="SiteService" />
<property name="nodeService" ref="NodeService" />
<property name="personService" ref="PersonService" />

View File

@@ -26,12 +26,15 @@
package org.alfresco.rest.api.tests;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.ContentLimitProvider;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.ResetPasswordServiceImpl;
import org.alfresco.rest.api.Renditions;
import org.alfresco.rest.api.model.Client;
import org.alfresco.rest.api.model.LoginTicket;
import org.alfresco.rest.api.model.LoginTicketResponse;
import org.alfresco.rest.api.model.PasswordReset;
import org.alfresco.rest.api.model.Rendition;
import org.alfresco.rest.api.tests.RepoService.TestNetwork;
import org.alfresco.rest.api.tests.client.HttpResponse;
import org.alfresco.rest.api.tests.client.Pair;
@@ -42,21 +45,32 @@ import org.alfresco.rest.api.tests.client.RequestContext;
import org.alfresco.rest.api.tests.client.data.Company;
import org.alfresco.rest.api.tests.client.data.JSONAble;
import org.alfresco.rest.api.tests.client.data.Person;
import org.alfresco.util.email.EmailUtil;
import org.alfresco.rest.api.tests.util.RestApiUtil;
import org.alfresco.service.cmr.preference.PreferenceService;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
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.security.MutableAuthenticationService;
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.GUID;
import org.alfresco.util.email.EmailUtil;
import org.apache.commons.httpclient.HttpStatus;
import org.json.simple.JSONObject;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import javax.mail.internet.MimeMessage;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
@@ -69,8 +83,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.mail.internet.MimeMessage;
import java.util.stream.Collectors;
import static org.alfresco.repo.security.authentication.ResetPasswordServiceImplTest.getWorkflowIdAndKeyFromUrl;
import static org.junit.Assert.assertEquals;
@@ -1920,6 +1933,360 @@ public class TestPeople extends AbstractBaseApiTest
return URL_PEOPLE + '/' + userId + "/reset-password";
}
@Test
public void retrieveAvatar() throws Exception
{
final String person1 = account1PersonIt.next();
publicApiClient.setRequestContext(new RequestContext(account1.getId(), person1));
AuthenticationUtil.setFullyAuthenticatedUser(person1);
NodeRef person1Ref = personService.getPerson(person1, false);
// No avatar, but valid person
{
deleteAvatarDirect(person1Ref);
assertNotNull(people.getPerson(person1)); // Pre-condition of test case
people.getAvatar(person1, false, 404);
}
// No avatar, but person exists and placeholder requested
{
assertNotNull(people.getPerson(person1)); // Pre-condition of test case
people.getAvatar(person1, true, 200);
}
// Non-existent person
{
String nonPerson = "i-do-not-exist";
people.getPerson(nonPerson, 404); // Pre-condition of test case
people.getAvatar(nonPerson, false, 404);
}
// Placeholder requested, but non-existent person
{
String nonPerson = "i-do-not-exist";
people.getPerson(nonPerson, 404); // Pre-condition of test case
people.getAvatar(nonPerson, true, 404);
}
// Avatar exists
{
// Create avatar - direct (i.e. not using the API, so that tests for get avatar can be separated from upload)
// There's no significance to the image being used here, it was the most suitable I could find.
ClassPathResource thumbRes = new ClassPathResource("test.jpg");
deleteAvatarDirect(person1Ref);
createAvatarDirect(person1Ref, thumbRes.getFile());
// Get avatar - API call
people.getAvatar(person1, false, 200);
}
// -me- alias
{
people.getAvatar("-me-", false, 200);
}
// If-Modified-Since behaviour
{
HttpResponse response = people.getAvatar(person1, false, 200);
Map<String, String> responseHeaders = response.getHeaders();
// Test 304 response
String lastModified = responseHeaders.get(LAST_MODIFIED_HEADER);
assertNotNull(lastModified);
// Has it been modified since the time it was last modified - no!
people.getAvatar(person1, lastModified, 304);
// Create an updated avatar
waitMillis(2000); // ensure time has passed between updates
ClassPathResource thumbRes = new ClassPathResource("publicapi/upload/quick.jpg");
deleteAvatarDirect(person1Ref);
createAvatarDirect(person1Ref, thumbRes.getFile());
people.getAvatar(person1, lastModified, 200);
}
// Attachment param
{
// No attachment parameter (default true)
Boolean attachmentParam = null;
HttpResponse response = people.getAvatar(person1, attachmentParam, false, null, 200);
Map<String, String> responseHeaders = response.getHeaders();
String contentDisposition = responseHeaders.get("Content-Disposition");
assertNotNull(contentDisposition);
assertTrue(contentDisposition.startsWith("attachment;"));
// attachment=true
attachmentParam = true;
response = people.getAvatar(person1, attachmentParam, false, null, 200);
responseHeaders = response.getHeaders();
contentDisposition = responseHeaders.get("Content-Disposition");
assertNotNull(contentDisposition);
assertTrue(contentDisposition.startsWith("attachment;"));
// attachment=false
attachmentParam = false;
response = people.getAvatar(person1, attachmentParam, false, null, 200);
responseHeaders = response.getHeaders();
contentDisposition = responseHeaders.get("Content-Disposition");
assertNull(contentDisposition);
}
}
private void waitMillis(int requiredDelay)
{
long startTime = System.currentTimeMillis();
long currTime = startTime;
while (currTime < (startTime + requiredDelay))
{
try
{
Thread.sleep(requiredDelay);
}
catch (InterruptedException e)
{
System.out.println(":>>> " + e.getMessage());
}
finally
{
currTime = System.currentTimeMillis();
System.out.println(":>>> waited "+(currTime-startTime) + "ms");
}
}
}
private void deleteAvatarDirect(NodeRef personRef)
{
List<ChildAssociationRef> assocs = nodeService.getChildAssocs(personRef).
stream().
filter(x -> x.getTypeQName().equals(ContentModel.ASSOC_PREFERENCE_IMAGE)).
collect(Collectors.toList());
if (assocs.size() > 0)
{
nodeService.deleteNode(assocs.get(0).getChildRef());
}
// remove old association if it exists
List<AssociationRef> refs = nodeService.getTargetAssocs(personRef, ContentModel.ASSOC_AVATAR);
if (refs.size() == 1)
{
NodeRef existingRef = refs.get(0).getTargetRef();
nodeService.removeAssociation(
personRef, existingRef, ContentModel.ASSOC_AVATAR);
}
if (assocs.size() > 1 || refs.size() > 1)
{
fail(String.format("Pref images: %d, Avatar assocs: %d", assocs.size(), refs.size()));
}
}
private NodeRef createAvatarDirect(NodeRef personRef, File avatarFile)
{
// create new avatar node
nodeService.addAspect(personRef, ContentModel.ASPECT_PREFERENCES, null);
ChildAssociationRef assoc = nodeService.createNode(
personRef,
ContentModel.ASSOC_PREFERENCE_IMAGE,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "origAvatar"),
ContentModel.TYPE_CONTENT);
final NodeRef avatarRef = assoc.getChildRef();
// JSF client compatibility?
nodeService.createAssociation(personRef, avatarRef, ContentModel.ASSOC_AVATAR);
// upload the avatar content
ContentService contentService = applicationContext.getBean("ContentService", ContentService.class);
ContentWriter writer = contentService.getWriter(avatarRef, ContentModel.PROP_CONTENT, true);
writer.guessMimetype(avatarFile.getName());
writer.putContent(avatarFile);
Rendition avatarR = new Rendition();
avatarR.setId("avatar");
Renditions renditions = applicationContext.getBean("Renditions", Renditions.class);
renditions.createRendition(avatarRef.getId(), avatarR, false, null);
return avatarRef;
}
@Test
public void updateAvatar() throws PublicApiException, IOException
{
final String person1 = account1PersonIt.next();
final String person2 = account1PersonIt.next();
publicApiClient.setRequestContext(new RequestContext(account1.getId(), person2));
AuthenticationUtil.setFullyAuthenticatedUser(person2);
// Update allowed when no existing avatar
{
// Pre-condition: no avatar exists
NodeRef personRef = personService.getPerson(person2, false);
deleteAvatarDirect(personRef);
people.getAvatar(person2, false, 404);
// TODO: What do we expect the 200 response body to be? Currently it's the person JSON - doesn't seem right.
ClassPathResource avatar = new ClassPathResource("publicapi/upload/quick.jpg");
HttpResponse response = people.updateAvatar(person2, avatar.getFile(), 200);
// TODO: ideally, this should be a "direct" retrieval to isolate update from get
people.getAvatar(person2, false, 200);
}
// Update existing avatar
{
// Pre-condition: avatar exists
people.getAvatar(person2, false, 200);
ClassPathResource avatar = new ClassPathResource("test.jpg");
HttpResponse response = people.updateAvatar(person2, avatar.getFile(), 200);
people.getAvatar(person2, false, 200);
// -me- alias
people.updateAvatar(person2, avatar.getFile(), 200);
people.getAvatar("-me-", false, 200);
}
// 400: invalid user ID
{
ClassPathResource avatar = new ClassPathResource("publicapi/upload/quick.jpg");
people.updateAvatar("joe@@bloggs.example.com", avatar.getFile(), 404);
}
// 401: authentication failure
{
publicApiClient.setRequestContext(new RequestContext(account1.getId(), account1Admin, "Wr0ngP4ssw0rd!"));
ClassPathResource avatar = new ClassPathResource("publicapi/upload/quick.jpg");
people.updateAvatar(account1Admin, avatar.getFile(), 401);
}
// 403: permission denied
{
publicApiClient.setRequestContext(new RequestContext(account1.getId(), person1));
ClassPathResource avatar = new ClassPathResource("publicapi/upload/quick.jpg");
people.updateAvatar(person2, avatar.getFile(), 403);
// Person can update themself
people.updateAvatar(person1, avatar.getFile(), 200);
// Admin can update someone else
publicApiClient.setRequestContext(new RequestContext(account1.getId(), account1Admin, "admin"));
people.updateAvatar(person1, avatar.getFile(), 200);
}
// 404: non-existent person
{
publicApiClient.setRequestContext(new RequestContext(account1.getId(), person1));
// Pre-condition: non-existent person
String nonPerson = "joebloggs@"+account1.getId();
people.getPerson(nonPerson, 404);
ClassPathResource avatar = new ClassPathResource("publicapi/upload/quick.jpg");
people.updateAvatar(nonPerson, avatar.getFile(), 404);
}
// 413: content exceeds individual file size limit
{
// Test content size limit
final ContentLimitProvider.SimpleFixedLimitProvider limitProvider = applicationContext.
getBean("defaultContentLimitProvider", ContentLimitProvider.SimpleFixedLimitProvider.class);
final long defaultSizeLimit = limitProvider.getSizeLimit();
limitProvider.setSizeLimitString("20000"); //20 KB
try
{
ClassPathResource avatar = new ClassPathResource("publicapi/upload/quick.jpg"); // ~26K
people.updateAvatar(person1, avatar.getFile(), 413);
}
finally
{
limitProvider.setSizeLimitString(Long.toString(defaultSizeLimit));
}
}
// 501: thumbnails disabled
{
ThumbnailService thumbnailService = applicationContext.getBean("thumbnailService", ThumbnailService.class);
// Disable thumbnail generation
thumbnailService.setThumbnailsEnabled(false);
try
{
ClassPathResource avatar = new ClassPathResource("publicapi/upload/quick.jpg");
people.updateAvatar(person1, avatar.getFile(), 501);
}
finally
{
thumbnailService.setThumbnailsEnabled(true);
}
}
}
@Test
public void removeAvatar() throws IOException, PublicApiException{
final String person1 = account1PersonIt.next();
final String person2 = account1PersonIt.next();
publicApiClient.setRequestContext(new RequestContext(account1.getId(), person1));
// Avatar exists
{
AuthenticationUtil.setFullyAuthenticatedUser("admin@"+account1.getId());
// Create avatar - direct (i.e. not using the API, so that tests for get avatar can be separated from upload)
// There's no significance to the image being used here, it was the most suitable I could find.
ClassPathResource thumbRes = new ClassPathResource("publicapi/upload/quick.jpg");
NodeRef personRef = personService.getPerson(person1, false);
deleteAvatarDirect(personRef);
createAvatarDirect(personRef, thumbRes.getFile());
// Get avatar - API call
people.getAvatar(person1, false, 200);
//remove avatar avatar exists
people.deleteAvatarImage(person1,204);
}
// Non-existent person
{
String nonPerson = "i-do-not-exist";
people.getPerson(nonPerson, 404); // Pre-condition of test case
people.deleteAvatarImage(nonPerson, 404);
}
//Authentication failed 401
{
setRequestContext(account1.getId(), networkAdmin, "wrongPassword");
people.deleteAvatarImage(person1,HttpServletResponse.SC_UNAUTHORIZED);
}
//No permission
{
publicApiClient.setRequestContext(new RequestContext(account1.getId(), person1));
AuthenticationUtil.setFullyAuthenticatedUser("admin@"+account1.getId());
// Create avatar - direct (i.e. not using the API, so that tests for get avatar can be separated from upload)
// There's no significance to the image being used here, it was the most suitable I could find.
ClassPathResource thumbRes = new ClassPathResource("test.jpg");
NodeRef personRef = personService.getPerson(person1, false);
deleteAvatarDirect(personRef);
createAvatarDirect(personRef, thumbRes.getFile());
people.deleteAvatarImage(person2, 403);
}
}
@Override
public String getScope()
{

View File

@@ -27,6 +27,7 @@ package org.alfresco.rest.api.tests.client;
import static org.junit.Assert.assertNotNull;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
@@ -47,7 +48,6 @@ import org.alfresco.rest.api.tests.client.data.AuditEntry;
import org.alfresco.rest.api.model.SiteUpdate;
import org.alfresco.rest.api.tests.TestPeople;
import org.alfresco.rest.api.tests.TestSites;
import org.alfresco.rest.api.tests.client.PublicApiClient.ListResponse;
import org.alfresco.rest.api.tests.client.PublicApiHttpClient.BinaryPayload;
import org.alfresco.rest.api.tests.client.PublicApiHttpClient.RequestBuilder;
import org.alfresco.rest.api.tests.client.data.Activities;
@@ -546,15 +546,20 @@ public class PublicApiClient
return response;
}
public HttpResponse get(String scope, String entityCollectionName, Object entityId, String relationCollectionName, Object relationshipEntityId, Map<String, String> params) throws IOException
public HttpResponse get(String scope, String entityCollectionName, Object entityId, String relationCollectionName, Object relationshipEntityId, Map<String, String> params, Map<String, String> headers) throws IOException
{
HttpResponse response = client.get(getRequestContext(), scope, entityCollectionName, entityId, relationCollectionName, relationshipEntityId, params);
HttpResponse response = client.get(getRequestContext(), scope, entityCollectionName, entityId, relationCollectionName, relationshipEntityId, params, headers);
logger.debug(response.toString());
return response;
}
public HttpResponse get(String scope, String entityCollectionName, Object entityId, String relationCollectionName, Object relationshipEntityId, Map<String, String> params) throws IOException
{
return get(scope, entityCollectionName, entityId, relationCollectionName, relationshipEntityId, params, null);
}
public HttpResponse getWithPassword(String scope, String password, String entityCollectionName, Object entityId, String relationCollectionName, Object relationshipEntityId, Map<String, String> params) throws IOException
{
HttpResponse response = client.get(getRequestContext(), scope, password, entityCollectionName, entityId, relationCollectionName, relationshipEntityId, params);
@@ -728,6 +733,21 @@ public class PublicApiClient
}
}
public HttpResponse getSingle(String entityCollectionName, String entityId, String relationCollectionName, String relationId, Map<String, String> params,
Map<String, String> headers, String errorMessage, int expectedStatus) throws PublicApiException
{
try
{
HttpResponse response = get("public", entityCollectionName, entityId, relationCollectionName, relationId, params, headers);
checkStatus(errorMessage, expectedStatus, response);
return response;
}
catch (IOException e)
{
throw new PublicApiException(e);
}
}
public HttpResponse getSingle(String entityCollectionName, String entityId, String relationCollectionName, String relationId, Map<String, String> params,
String errorMessage, int expectedStatus) throws PublicApiException
{
@@ -1305,6 +1325,59 @@ public class PublicApiClient
{
remove("people", personId, "activities", String.valueOf(activity.getId()), "Failed to remove activity");
}
public HttpResponse getAvatar(String personId, boolean placeholder, int expectedStatus) throws PublicApiException
{
return getAvatar(personId, null, placeholder, null, expectedStatus);
}
public HttpResponse getAvatar(String personId, String ifModifiedSince, int expectedStatus) throws PublicApiException
{
return getAvatar(personId, null, false, ifModifiedSince, expectedStatus);
}
public HttpResponse getAvatar(String personId, Boolean attachment, boolean placeholder, String ifModifiedSince, int expectedStatus) throws PublicApiException
{
// Binary response expected
Map<String, String> params = new HashMap<>();
params.put("placeholder", Boolean.toString(placeholder));
// Optional attachment parameter
if (attachment != null)
{
params.put("attachment", attachment.toString());
}
Map<String, String> headers = new HashMap<>();
if (ifModifiedSince != null)
{
headers.put("If-Modified-Since", ifModifiedSince);
}
HttpResponse response = getSingle("people", personId, "avatar", null, params, headers, "Failed to get avatar", expectedStatus);
checkStatus("Unexpected response", expectedStatus, response);
return response;
}
public HttpResponse updateAvatar(String personId, File avatar, int expectedStatus) throws PublicApiException
{
try
{
Map<String, String> params = new HashMap<>();
BinaryPayload payload = new BinaryPayload(avatar);
HttpResponse response = client.putBinary(getRequestContext(), "public", 1, "people", personId, "avatar", null, payload, params);
checkStatus("Unexpected status", expectedStatus, response);
return response;
}
catch(IOException e)
{
throw new PublicApiException(e);
}
}
public void deleteAvatarImage(String personId, int expectedStatus) throws PublicApiException{
remove("people", personId, "avatar", null, null, "Failed to remove avatar image", expectedStatus);
}
}
public class Comments extends AbstractProxy

View File

@@ -34,6 +34,7 @@ import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
@@ -312,17 +313,34 @@ public class PublicApiHttpClient
public HttpResponse get(final RequestContext rq, String scope, final String entityCollectionName, final Object entityId,
final String relationCollectionName, final Object relationshipEntityId, Map<String, String> params) throws IOException
{
return get(rq, scope, 1, entityCollectionName, entityId, relationCollectionName, relationshipEntityId, params);
return get(rq, scope, 1, entityCollectionName, entityId, relationCollectionName, relationshipEntityId, params, null);
}
public HttpResponse get(final RequestContext rq, String scope, final String entityCollectionName, final Object entityId,
final String relationCollectionName, final Object relationshipEntityId, Map<String, String> params, Map<String, String> headers) throws IOException
{
return get(rq, scope, 1, entityCollectionName, entityId, relationCollectionName, relationshipEntityId, params, headers);
}
public HttpResponse get(final RequestContext rq, final String scope, final int version, final String entityCollectionName, final Object entityId,
final String relationCollectionName, final Object relationshipEntityId, Map<String, String> params) throws IOException
final String relationCollectionName, final Object relationshipEntityId, Map<String, String> params, Map<String, String> headers) throws IOException
{
if (headers == null)
{
headers = Collections.emptyMap();
}
RestApiEndpoint endpoint = new RestApiEndpoint(rq.getNetworkId(), scope, version, entityCollectionName, entityId, relationCollectionName,
relationshipEntityId, params);
String url = endpoint.getUrl();
GetMethod req = new GetMethod(url);
for (Entry<String, String> header : headers.entrySet())
{
req.addRequestHeader(header.getKey(), header.getValue());
}
return submitRequest(req, rq);
}

View File

@@ -0,0 +1,44 @@
/*
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2017 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.api.tests.client.data;
import org.json.simple.JSONObject;
import java.io.Serializable;
public class Avatar implements Serializable, ExpectedComparison
{
@Override
public void expected(Object other)
{
}
public static Avatar parseAvatar(JSONObject entry)
{
return new Avatar();
}
}