PeopleRestService.java

/*
 * Copyright (C) 2003-2010 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 */
package org.exoplatform.social.service.rest;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.shindig.social.opensocial.model.Activity;
import org.exoplatform.commons.utils.ListAccess;
import org.exoplatform.container.ExoContainer;
import org.exoplatform.container.ExoContainerContext;
import org.exoplatform.container.PortalContainer;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.rest.resource.ResourceContainer;
import org.exoplatform.services.security.ConversationState;
import org.exoplatform.services.security.IdentityConstants;
import org.exoplatform.social.common.RealtimeListAccess;
import org.exoplatform.social.core.activity.model.ExoSocialActivity;
import org.exoplatform.social.core.identity.model.Identity;
import org.exoplatform.social.core.identity.model.Profile;
import org.exoplatform.social.core.identity.provider.OrganizationIdentityProvider;
import org.exoplatform.social.core.identity.provider.SpaceIdentityProvider;
import org.exoplatform.social.core.manager.ActivityManager;
import org.exoplatform.social.core.manager.IdentityManager;
import org.exoplatform.social.core.manager.RelationshipManager;
import org.exoplatform.social.core.profile.ProfileFilter;
import org.exoplatform.social.core.relationship.model.Relationship;
import org.exoplatform.social.core.service.LinkProvider;
import org.exoplatform.social.core.space.SpaceException;
import org.exoplatform.social.core.space.SpaceFilter;
import org.exoplatform.social.core.space.model.Space;
import org.exoplatform.social.core.space.spi.SpaceService;
import org.exoplatform.social.rest.impl.user.UserRestResourcesV1;
import org.exoplatform.social.service.rest.api.models.IdentityNameList;
import org.exoplatform.social.service.rest.api.models.IdentityNameList.Option;
import org.exoplatform.social.service.rest.api.models.PeopleInfo;
import org.exoplatform.webui.utils.TimeConvertUtils;

import javax.annotation.security.RolesAllowed;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.ws.rs.core.Response.Status;
import java.util.*;

/**
 * 
 * Provides REST Services for manipulating jobs relates to people.
 *
 * @anchor PeopleRestService
 */

@Path("social/people")
public class PeopleRestService implements ResourceContainer{
  private static final String SPACE_PREFIX = "space::";
  /** Confirmed Status information */
  private static final String CONFIRMED_STATUS = "confirmed";
  /** Pending Status information */
  private static final String PENDING_STATUS = "pending";
  /** Incoming Status information */
  private static final String INCOMING_STATUS = "incoming";
  /** Ignored Status information */
  private static final String IGNORED_STATUS = "ignored";
  /** Waiting Status information */
  private static final String WAITING_STATUS = "waiting";
  /** Alien Status information */
  private static final String ALIEN_STATUS = "alien";
  /** Invite action */
  private static final String INVITE_ACTION = "Invite";
  /** Accept action */
  private static final String ACCEPT_ACTION = "Accept";
  /** Deny action */
  private static final String DENY_ACTION = "Deny";
  /** Revoke action */
  private static final String REVOKE_ACTION = "Revoke";
  /** Remove action */
  private static final String REMOVE_ACTION = "Disconnect";
  /** Member of space Status information */
  private static final String SPACE_MEMBER = "member_of_space";
  /** User to invite to join the space Status information */
  private static final String USER_TO_INVITE = "user_to_invite";
  /** User or space to share document with */
  private static final String SHARE_DOCUMENT = "share_document";
  /** User to mention in a comment */
  private static final String MENTION_COMMENT = "mention_comment";
  /** User mentioned in an activity stream */
  private static final String MENTION_ACTIVITY_STREAM = "mention_activity_stream";
  /** No action */
  private static final String NO_ACTION = "NoAction";
  /** No information */
  private static final String NO_INFO = "NoInfo";
  /** Number of user names is added to suggest list. */
  private static final long SUGGEST_LIMIT = 20;

  /** Number of default limit activities. */
  private static final String DEFAULT_ACTIVITY = "DEFAULT_ACTIVITY";
  private static final String LINK_ACTIVITY = "LINK_ACTIVITY";
  private static final String DOC_ACTIVITY = "DOC_ACTIVITY";
  private static final Log LOG = ExoLogger.getLogger(PeopleRestService.class);

  private IdentityManager identityManager;
  private ActivityManager activityManager;
  private RelationshipManager relationshipManager;
  private SpaceService spaceService;

  public PeopleRestService() {
  }

  /**
   * Gets users' names that match the input string for suggestion.
   *
   * @param uriInfo The requested URI information.
   * @param name The provided characters to be searched.
   * @param currentUser The user who sends request.
   * @param activityId the Id of the activity where we want to mention a user in its comment
   * @param typeOfRelation The relationship status such as "confirmed", "pending", "incoming", "member_of_space", "mention_activity_stream", "mention_comment" or "user_to_invite"
   * @param spaceURL The URL of the related space.
   * @param format The format of the returned result, for example, JSON, or XML.
   * @return A list of users' names that match the input string.
   * @throws Exception
   * @LevelAPI Platform
   * @anchor PeopleRestService.suggestUsernames
   */
  @SuppressWarnings("deprecation")
  @RolesAllowed("users")
  @GET
  @Path("suggest.{format}")
  public Response suggestUsernames(@Context UriInfo uriInfo,
                    @QueryParam("nameToSearch") String name,
                    @QueryParam("currentUser") String currentUser,
                    @QueryParam("typeOfRelation") String typeOfRelation,
                    @QueryParam("activityId") String activityId,
                    @QueryParam("spaceURL") String spaceURL,
                    @PathParam("format") String format) throws Exception {
    String[] mediaTypes = new String[] { "json", "xml" };
    MediaType mediaType = Util.getMediaType(format, mediaTypes);

    ProfileFilter identityFilter = new ProfileFilter();

    identityFilter.setName(name);
    identityFilter.setCompany("");
    identityFilter.setPosition("");
    identityFilter.setSkills("");
    Space currentSpace = getSpaceService().getSpaceByUrl(spaceURL);
    Activity currentActivity = getActivityManager().getActivity(activityId);

    List<Identity> excludedIdentityList = identityFilter.getExcludedIdentityList();
    if (excludedIdentityList == null) {
      excludedIdentityList = new ArrayList<Identity>();
    }
    IdentityNameList nameList = new IdentityNameList();
    Identity currentIdentity = Util.getViewerIdentity(currentUser);
    identityFilter.setViewerIdentity(currentIdentity);

    Identity[] result;
    if (PENDING_STATUS.equals(typeOfRelation)) {
      ListAccess<Identity> listAccess = getRelationshipManager().getOutgoingByFilter(currentIdentity, identityFilter);
      result = listAccess.load(0, (int)SUGGEST_LIMIT);
      nameList.addToNameList(result);
    } else if (INCOMING_STATUS.equals(typeOfRelation)) {
      ListAccess<Identity> listAccess = getRelationshipManager().getIncomingByFilter(currentIdentity, identityFilter);
      result = listAccess.load(0, (int)SUGGEST_LIMIT);
      nameList.addToNameList(result);
    } else if (CONFIRMED_STATUS.equals(typeOfRelation)){
      ListAccess<Identity> listAccess = getRelationshipManager().getConnectionsByFilter(currentIdentity, identityFilter);
      result = listAccess.load(0, (int)SUGGEST_LIMIT);
      nameList.addToNameList(result);
    } else if (SPACE_MEMBER.equals(typeOfRelation)) {  // Use in search space member
      List<Identity> identities = Arrays.asList(getIdentityManager().getIdentitiesByProfileFilter(OrganizationIdentityProvider.NAME, identityFilter, false).load(0, (int)SUGGEST_LIMIT));
      Space space = getSpaceService().getSpaceByUrl(spaceURL);
      addSpaceOrUserToList(identities, nameList, space, typeOfRelation, 0);
    } else if (USER_TO_INVITE.equals(typeOfRelation)) {
      Space space = getSpaceService().getSpaceByUrl(spaceURL);

      // This is for pre-loading data
      if (name != null && name.contains(",")) {
        String[] items = name.split(",");
        for (String item : items) {
          Option opt = new Option();
          if (item.startsWith(SPACE_PREFIX)) {
            Space s = getSpaceService().getSpaceByPrettyName(item.substring(7));
            opt.setType("space");
            opt.setValue(SPACE_PREFIX + s.getPrettyName());
            opt.setText(s.getDisplayName());
            opt.setAvatarUrl(s.getAvatarUrl());
            opt.setOrder(3);
          } else {
            Identity identity = getIdentityManager().getOrCreateIdentity(
                                                     OrganizationIdentityProvider.NAME, item, false);
            opt.setType("user");
            opt.setOrder(1);
            if (identity != null) {
              Profile p = identity.getProfile();
              opt.setValue((String) p.getProperty(Profile.USERNAME));
              opt.setText(p.getFullName() + " (" + (String) p.getProperty(Profile.USERNAME) + ")");
              opt.setAvatarUrl(p.getAvatarUrl());
            } else {
              opt.setValue(item);
              opt.setText(item);
              opt.setInvalid(true);
            }
          }
          nameList.addOption(opt);
        }

        return Util.getResponse(nameList, uriInfo, mediaType, Response.Status.OK);
      }

      // Search in connections first
      ListAccess<Identity> connections = getRelationshipManager().getConnectionsByFilter(currentIdentity, identityFilter);
      if (connections != null && connections.getSize() > 0) {
        int size = connections.getSize();
        Identity[] identities = connections.load(0, size < SUGGEST_LIMIT ? size : (int)SUGGEST_LIMIT);
        for (Identity id : identities) {
          addSpaceOrUserToList(Arrays.asList(id), nameList, space, typeOfRelation, 1);
          excludedIdentityList.add(id);
        }
      }

      List<Space> exclusions = new ArrayList<Space>();
      // Includes spaces the current user is member.
      long remain = SUGGEST_LIMIT - (nameList.getOptions() != null ? nameList.getOptions().size() : 0);
      if (remain > 0) {
        identityFilter.setExcludedIdentityList(excludedIdentityList);
        ListAccess<Identity> listAccess = getIdentityManager().getIdentitiesByProfileFilter(OrganizationIdentityProvider.NAME, identityFilter, false);
        List<Identity> identities = Arrays.asList(listAccess.load(0, (int) remain));
        addSpaceOrUserToList(identities, nameList, space, typeOfRelation, 2);
      }

      remain = SUGGEST_LIMIT - (nameList.getOptions() != null ? nameList.getOptions().size() : 0);
      if (remain > 0) {
        SpaceFilter spaceFilter = new SpaceFilter();
        spaceFilter.setSpaceNameSearchCondition(name);
        ListAccess<Space> list = getSpaceService().getMemberSpacesByFilter(currentUser, spaceFilter);
        Space[] spaces = list.load(0, (int) remain);
        for (Space s : spaces) {
          //do not add current space
          if (s.equals(space)) {
            exclusions.add(s);
            continue;
          }
          Option opt = new Option();
          opt.setType("space");
          opt.setValue(SPACE_PREFIX + s.getPrettyName());
          opt.setText(s.getDisplayName());
          opt.setAvatarUrl(s.getAvatarUrl());
          opt.setOrder(3);
          nameList.addOption(opt);
          exclusions.add(s);
        }
      }

      // Adding all non hidden spaces.
      remain = SUGGEST_LIMIT - (nameList.getOptions() != null ? nameList.getOptions().size() : 0);
      if (remain > 0) {
        SpaceFilter spaceFilter = new SpaceFilter();
        spaceFilter.setSpaceNameSearchCondition(name);
        spaceFilter.addExclusions(exclusions);
        ListAccess<Space> list = getSpaceService().getVisibleSpacesWithListAccess(currentUser, spaceFilter);
        Space[] spaces = list.load(0, (int) remain);
        for (Space s : spaces) {
          if (s.equals(space)) {
            //do not add current space
            exclusions.add(s);
            continue;
          }
          Option opt = new Option();
          opt.setType("space");
          opt.setValue(SPACE_PREFIX + s.getPrettyName());
          opt.setText(s.getDisplayName());
          opt.setAvatarUrl(s.getAvatarUrl());
          opt.setOrder(4);
          nameList.addOption(opt);
        }
      }
    } else if (SHARE_DOCUMENT.equals(typeOfRelation)) {

      // This is for pre-loading data
      if (name != null && name.contains(",")) {
        String[] items = name.split(",");
        for (String item : items) {
          Option opt = new Option();
          if (item.startsWith(SPACE_PREFIX)) {
            Space s = getSpaceService().getSpaceByPrettyName(item.substring(7));
            opt.setType("space");
            opt.setValue(SPACE_PREFIX + s.getPrettyName());
            opt.setText(s.getDisplayName());
            opt.setAvatarUrl(s.getAvatarUrl());
            opt.setOrder(2);
          } else {
            Identity identity = getIdentityManager().getOrCreateIdentity(
                OrganizationIdentityProvider.NAME, item, false);
            opt.setType("user");
            opt.setOrder(1);
            if (identity != null) {
              Profile p = identity.getProfile();
              opt.setValue((String) p.getProperty(Profile.USERNAME));
              opt.setText(p.getFullName());
              opt.setAvatarUrl(p.getAvatarUrl());
            } else {
              opt.setValue(item);
              opt.setText(item);
              opt.setInvalid(true);
            }
          }
          nameList.addOption(opt);
        }

        return Util.getResponse(nameList, uriInfo, mediaType, Response.Status.OK);
      }

      // Search in connections first
      ListAccess<Identity> connections = getRelationshipManager().getConnectionsByFilter(currentIdentity, identityFilter);
      if (connections != null && connections.getSize() > 0) {
        int size = connections.getSize();
        Identity[] identities = connections.load(0, size < SUGGEST_LIMIT ? size : (int)SUGGEST_LIMIT);
        for (Identity id : identities) {
          Option opt = new Option();
          String fullName = id.getProfile().getFullName();
          String userName = (String) id.getProfile().getProperty(Profile.USERNAME);
          opt.setType("user");
          opt.setValue(userName);
          opt.setText(fullName + " (" + userName + ")");
          opt.setAvatarUrl(id.getProfile() == null ? null : id.getProfile().getAvatarUrl());
          excludedIdentityList.add(id);
          opt.setOrder(1);
          nameList.addOption(opt);
        }
      }

      List<Space> exclusions = new ArrayList<Space>();
      // Includes spaces the current user is member.
      long remain = SUGGEST_LIMIT - (nameList.getOptions() != null ? nameList.getOptions().size() : 0);
      if (remain > 0) {
        SpaceFilter spaceFilter = new SpaceFilter();
        spaceFilter.setSpaceNameSearchCondition(name);
        ListAccess<Space> list = getSpaceService().getMemberSpacesByFilter(currentUser, spaceFilter);
        Space[] spaces = list.load(0, (int) remain);
        for (Space s : spaces) {
          Option opt = new Option();
          opt.setType("space");
          opt.setValue(SPACE_PREFIX + s.getPrettyName());
          opt.setText(s.getDisplayName());
          opt.setAvatarUrl(s.getAvatarUrl());
          opt.setOrder(2);
          nameList.addOption(opt);
          exclusions.add(s);
        }
      }
      remain = SUGGEST_LIMIT - (nameList.getOptions() != null ? nameList.getOptions().size() : 0);
      if (remain > 0) {
        identityFilter.setExcludedIdentityList(excludedIdentityList);
        ListAccess<Identity> listAccess = getIdentityManager().getIdentitiesByProfileFilter(OrganizationIdentityProvider.NAME, identityFilter, false);
        List<Identity> identities = Arrays.asList(listAccess.load(0, (int) remain));
        for (Identity id : identities) {
          Option opt = new Option();
          String fullName = id.getProfile().getFullName();
          String userName = (String) id.getProfile().getProperty(Profile.USERNAME);
          opt.setType("user");
          opt.setValue(userName);
          opt.setText(fullName);
          opt.setAvatarUrl(id.getProfile() == null ? null : id.getProfile().getAvatarUrl());
          excludedIdentityList.add(id);
          opt.setOrder(4);
          nameList.addOption(opt);
        }
      }
    } else if (MENTION_ACTIVITY_STREAM.equals(typeOfRelation)) {
      LinkedHashSet<UserInfo> userInfos = new LinkedHashSet<UserInfo>();

      // first add space members in the suggestion list when mentioning in a space Activity Stream
      if (currentSpace != null) {
        userInfos = addSpaceMembers(spaceURL, identityFilter, userInfos, currentUser);
      }
      // then add connections in the suggestions
      long remain = SUGGEST_LIMIT - (userInfos != null ? userInfos.size() : 0);
      if (remain > 0) {
        userInfos = addUserConnections(currentIdentity, identityFilter, userInfos, currentUser, remain);
      }

      // finally add others users in the suggestions
      remain = SUGGEST_LIMIT - (userInfos != null ? userInfos.size() : 0);
      if (remain > 0) {
        userInfos = addOtherUsers(identityFilter, excludedIdentityList, userInfos, currentUser, remain);
      }
      return Util.getResponse(userInfos, uriInfo, mediaType, Response.Status.OK);

    } else if (MENTION_COMMENT.equals(typeOfRelation)) {
      LinkedHashSet<UserInfo> userInfos = new LinkedHashSet<UserInfo>();
      long remain = SUGGEST_LIMIT;

      if(activityId == null) {
        LOG.warn("Mentioning in activity comment : activity id parameter is null. the activity users will not be added in the result of mentioning.");
      } else {
        ExoSocialActivity activity = getActivityManager().getActivity(activityId);

        // first add the author in the suggestion
        String authorId = activity.getPosterId();
        userInfos = addUsernameToInfosList(authorId, identityFilter, userInfos, currentUser, true);

        // then add the commented users in the suggestion list
        userInfos = addCommentedUsers(activity, identityFilter, excludedIdentityList, userInfos, currentUser);

        // add the mentioned users in the suggestion
        remain = SUGGEST_LIMIT - (userInfos != null ? userInfos.size() : 0);
        if (remain > 0) {
          userInfos = addMentionedUsers(activity, identityFilter, excludedIdentityList, userInfos, currentUser);
        }

        // add the liked users in the suggestion
        remain = SUGGEST_LIMIT - (userInfos != null ? userInfos.size() : 0);
        if (remain > 0) {
          userInfos = addLikedUsers(activity, identityFilter, excludedIdentityList, userInfos, currentUser);
        }

      }

      // add space members in the suggestion list when mentioning in a comment in a space Activity Stream
      if (currentSpace != null && currentActivity != null) {
        remain = SUGGEST_LIMIT - (userInfos != null ? userInfos.size() : 0);
        if (remain > 0) {
          userInfos = addSpaceMembers(spaceURL, identityFilter, userInfos, currentUser);
        }
      }

      // add the connections in the suggestion
      remain = SUGGEST_LIMIT - (userInfos != null ? userInfos.size() : 0);
      if (remain > 0) {
        userInfos = addUserConnections(currentIdentity, identityFilter, userInfos, currentUser, remain);
      }

      // finally add others in the suggestion
      remain = SUGGEST_LIMIT - (userInfos != null ? userInfos.size() : 0);
      if (remain > 0) {
        userInfos = addOtherUsers(identityFilter, excludedIdentityList, userInfos, currentUser, remain);
      }

      return Util.getResponse(userInfos, uriInfo, mediaType, Response.Status.OK);

    } else { // Identities that match the keywords.
      result = getIdentityManager().getIdentityStorage().getIdentitiesForMentions(OrganizationIdentityProvider.NAME, identityFilter, null, 0L, SUGGEST_LIMIT, false).toArray(new Identity[0]);
      nameList.addToNameList(result);
    }

    return Util.getResponse(nameList, uriInfo, mediaType, Response.Status.OK);
  }

  private LinkedHashSet<UserInfo> addUsersToUserInfosList(Identity[] identities, ProfileFilter identityFilter, LinkedHashSet<UserInfo> userInfos, String currentUserId,  boolean filterByName) {
    for (Identity identity : identities) {
      userInfos = addUserToInfosList(identity, identityFilter, userInfos, currentUserId, filterByName);
    }
    return userInfos;
  }

  private LinkedHashSet<UserInfo> addUserToInfosList(Identity userIdentity, ProfileFilter identityFilter, LinkedHashSet<UserInfo> userInfos, String currentUserId, boolean filterByName) {
    if (!userIdentity.getProviderId().equals(OrganizationIdentityProvider.NAME)) {
      LOG.warn("Cannot add Identity to suggestion list. Identity with id '"+ userIdentity.getRemoteId() + "' is not of type 'user'");
      return userInfos;
    }
    if (userInfos.size() == SUGGEST_LIMIT) {
      return userInfos;
    }
    if (identityFilter.getExcludedIdentityList().contains(userIdentity)) {
      return userInfos;
    }
    if(filterByName && !userIdentity.getRemoteId().toLowerCase().contains(identityFilter.getName().toLowerCase()) && !userIdentity.getProfile().getFullName().toLowerCase().contains(identityFilter.getName().toLowerCase())) {
      return userInfos;
    }
    UserInfo user = new UserInfo();
    boolean isAnonymous = IdentityConstants.ANONIM.equals(currentUserId);
    if (!isAnonymous) {
      user.setId(userIdentity.getRemoteId());
    }
    user.setName(userIdentity.getProfile() == null ? null : userIdentity.getProfile().getFullName());
    user.setAvatar(userIdentity.getProfile() == null ? null : userIdentity.getProfile().getAvatarUrl());
    user.setType("contact");
    userInfos.add(user);
    return userInfos;
  }

  private LinkedHashSet<UserInfo> addUsernameToInfosList(String userId, ProfileFilter identityFilter, LinkedHashSet<UserInfo> userInfos, String currentUserId, boolean filterByName) {
    Identity userIdentity = getIdentityManager().getIdentity(userId, false);
    if (userIdentity == null) {
      userIdentity = getIdentityManager().getOrCreateIdentity(OrganizationIdentityProvider.NAME, userId, false);
    }
    if (userIdentity == null) {
      LOG.warn("Cannot find user identity with username = " + userId);
      return userInfos;
    }
    return addUserToInfosList(userIdentity, identityFilter, userInfos, currentUserId, filterByName);
  }

  private LinkedHashSet<UserInfo> addUserConnections (Identity currentIdentity, ProfileFilter identityFilter, LinkedHashSet<UserInfo> userInfos, String currentUser, long remain) throws Exception {
    ListAccess<Identity> connections = getRelationshipManager().getConnectionsByFilter(currentIdentity, identityFilter);
    if (connections != null && connections.getSize() > 0) {
      Identity[] identities = connections.load(0, (int) remain);
      userInfos = addUsersToUserInfosList(identities, identityFilter, userInfos, currentUser, false);
    }
    return userInfos;
  }

  private LinkedHashSet<UserInfo> addOtherUsers (ProfileFilter identityFilter, List<Identity> excludedIdentityList, LinkedHashSet<UserInfo> userInfos, String currentUser, long remain) throws Exception {
    List<Identity> listAccess = getIdentityManager().getIdentityStorage().getIdentitiesForMentions(OrganizationIdentityProvider.NAME, identityFilter, null, 0L, remain, false);
    identityFilter.setExcludedIdentityList(excludedIdentityList);
    Identity[] identitiesList = listAccess.toArray(new Identity[0]);
    userInfos = addUsersToUserInfosList(identitiesList, identityFilter, userInfos, currentUser, false);
    return userInfos;
  }

  private LinkedHashSet<UserInfo> addSpaceMembers (String spaceURL, ProfileFilter identityFilter, LinkedHashSet<UserInfo> userInfos, String currentUser) {
    String[] spaceMembers = getSpaceService().getSpaceByUrl(spaceURL).getMembers();
    for (String spaceMember : spaceMembers) {
      userInfos = addUsernameToInfosList(spaceMember, identityFilter,userInfos, currentUser, true);
    }
    return userInfos;
  }

  private LinkedHashSet<UserInfo> addCommentedUsers (ExoSocialActivity activity, ProfileFilter identityFilter, List<Identity> excludedIdentityList, LinkedHashSet<UserInfo> userInfos, String currentUser) {
    String[] commentedUsers = activity.getCommentedIds();
    for (String commentedUser : commentedUsers) {
      identityFilter.setExcludedIdentityList(excludedIdentityList);
      userInfos = addUsernameToInfosList(commentedUser, identityFilter, userInfos, currentUser, true);
    }
    return userInfos;
  }

  private LinkedHashSet<UserInfo> addMentionedUsers(ExoSocialActivity activity, ProfileFilter identityFilter, List<Identity> excludedIdentityList, LinkedHashSet<UserInfo> userInfos, String currentUser) {
    String[] mentionedUsers = activity.getMentionedIds();
    for (String mentionedUser : mentionedUsers) {
      identityFilter.setExcludedIdentityList(excludedIdentityList);
      userInfos = addUsernameToInfosList(mentionedUser, identityFilter, userInfos, currentUser, true);
    }
    return userInfos;
  }

  private LinkedHashSet<UserInfo> addLikedUsers(ExoSocialActivity activity, ProfileFilter identityFilter, List<Identity> excludedIdentityList, LinkedHashSet<UserInfo> userInfos, String currentUser) {
    String[] likedUsers = activity.getLikeIdentityIds();
    for (String likedUser : likedUsers) {
      identityFilter.setExcludedIdentityList(excludedIdentityList);
      userInfos = addUsernameToInfosList(likedUser, identityFilter, userInfos, currentUser, true);
    }
    return userInfos;
  }

  private void addSpaceOrUserToList(List<Identity> identities, IdentityNameList options,
                                   Space space, String typeOfRelation, int order) throws SpaceException {
    SpaceService spaceSrv = getSpaceService(); 
    for (Identity identity : identities) {
      String fullName = identity.getProfile().getFullName();
      String userName = (String) identity.getProfile().getProperty(Profile.USERNAME); 
      Option opt = new Option();
      if (SPACE_MEMBER.equals(typeOfRelation) && spaceSrv.isMember(space, userName)) {
        opt.setType("user");
        opt.setValue(fullName);
        opt.setText(fullName);
        opt.setAvatarUrl(identity.getProfile() == null ? null : identity.getProfile().getAvatarUrl());
      } else if (USER_TO_INVITE.equals(typeOfRelation) && (space == null || (!spaceSrv.isInvitedUser(space, userName)
                 && !spaceSrv.isPendingUser(space, userName) && !spaceSrv.isMember(space, userName)))) {
        opt.setType("user");
        opt.setValue(userName);
        opt.setText(fullName + " (" + userName + ")");
        opt.setAvatarUrl(identity.getProfile() == null ? null : identity.getProfile().getAvatarUrl());
      } else {
        continue;
      }
      opt.setOrder(order);
      options.addOption(opt);
    }
  }

  /**
   * Gets users' information that matches the input string.
   * 
   * @param uriInfo The request URI information.
   * @param query The name of the user to filter.
   * @return Users' information that matches the input string.
   * @throws Exception
   * @LevelAPI Platform
   * @anchor PeopleRestService.suggestUsernames
   * @deprecated Deprecated from 4.3.x. Replaced by a new API {@link UserRestResourcesV1#getUsers(UriInfo, String, String, String, int, int, boolean, String)}
   * 
   */
  @GET
  @Path("getprofile/data.json")
  @RolesAllowed("users")
  public Response suggestUsernames(@Context UriInfo uriInfo,
                                   @Context SecurityContext securityContext,
                    @QueryParam("search") String query) throws Exception {
    MediaType mediaType = Util.getMediaType("json", new String[]{"json"});
    ProfileFilter filter = new ProfileFilter();
    
    filter.setName(query);
    List<Identity> identities = Arrays.asList(getIdentityManager().getIdentitiesByProfileFilter(
                                  OrganizationIdentityProvider.NAME, filter, false).load(0, (int)SUGGEST_LIMIT));
    
    List<UserInfo> userInfos = new ArrayList<PeopleRestService.UserInfo>(identities.size());
    UserInfo userInfo;
    String userType = ConversationState.getCurrent().getIdentity().getUserId();
    boolean isAnonymous = IdentityConstants.ANONIM.equals(userType) 
      || securityContext.getUserPrincipal() == null;
    
    for (Identity identity : identities) {
      userInfo = new UserInfo();
      if (!isAnonymous) {
        userInfo.setId(identity.getRemoteId());
      }
      userInfo.setName(identity.getProfile().getFullName());
      userInfo.setAvatar(identity.getProfile() == null ? null : identity.getProfile().getAvatarUrl());
      userInfo.setType("contact"); //hardcode for test
      userInfos.add(userInfo);
    }
    
    return Util.getResponse(userInfos, uriInfo, mediaType, Response.Status.OK);
  }
  
  /**
   * Gets the information of people who have had connection with the current user.
   * 
   * @param uriInfo The requested URI information.
   * @param format The format of the returned result, for example, JSON, or XML.
   * @param portalName The name of current portal.
   * @param nameToSearch The name of user who wants to get.
   * @param offset The starting point of the returned result.
   * @param limit The ending point of the returned result.
   * @param lang The locale type.
   * @return The information of people who have had connection with the current user and the information must match with
   * the input condition (name to search).
   * @throws Exception
   * @LevelAPI Platform
   * @anchor PeopleRestService.searchConnection
   * @deprecated Deprecated from 4.3.x. Replaced by a new API {@link UserRestResourcesV1#getConnectionOfUser(UriInfo, String, boolean, String)}
   */
  @GET
  @Path("{portalName}/getConnections.{format}")
  @RolesAllowed("users")
  public Response searchConnection(@Context UriInfo uriInfo,
                                   @Context SecurityContext securityContext,
                    @PathParam("portalName") String portalName,
                    @QueryParam("nameToSearch") String nameToSearch,
                    @QueryParam("offset") int offset,
                    @QueryParam("limit") int limit,
                    @QueryParam("lang") String lang,
                    @PathParam("format") String format) throws Exception {
    String[] supportedMediaType = { "json" };
    MediaType mediaType = Util.getMediaType(format,supportedMediaType);

    activityManager = Util.getActivityManager(portalName);
    relationshipManager = Util.getRelationshipManager(portalName);
    identityManager = Util.getIdentityManager(portalName);
    
    Identity currentUser = Util.getIdentityManager(portalName).getOrCreateIdentity(OrganizationIdentityProvider.NAME,
                                                                                   Util.getViewerId(uriInfo), true);
    Identity[] identities;
    List<HashMap<String, Object>> entitys = new ArrayList<HashMap<String,Object>>();
    if (nameToSearch == null) { 
      // default loading, if load more then need to re-calculate offset and limit before going here via rest URL.     
      identities = getIdentityManager().getConnectionsWithListAccess(currentUser).load(offset, limit);
    } else { 
      // search
      nameToSearch = nameToSearch.trim();
      
      ProfileFilter filter = new ProfileFilter();
      filter.setName(nameToSearch);
      filter.setViewerIdentity(currentUser);
      // will be getConnectionsByProfileFilter
      identities = relationshipManager.getConnectionsByFilter(currentUser, filter).load(offset, limit);
    }
    
    String userType = ConversationState.getCurrent().getIdentity().getUserId();
    boolean isAnonymous = IdentityConstants.ANONIM.equals(userType) 
      || securityContext.getUserPrincipal() == null;
    
    for(Identity identity : identities){
      if (isAnonymous) {
        entitys.add(new ConnectionInfoRestOut(identity));
        continue;
      }
      
      HashMap<String, Object> temp = getIdentityInfo(identity, lang);
      if(temp != null){
        entitys.add(temp);
      }
    }
    
    return Util.getResponse(entitys, uriInfo, mediaType, Response.Status.OK);
  }
  
  /**
   * Gets a set of information of the target user. The returned information of the user includes full name, position
   * avatar, link to profile and relationship status with the current user who sends request.
   * 
   * @param uriInfo The requested URI information.
   * @param securityContext The security context of the system.
   * @param userId The Id of a specific user.
   * @param format The format of the returned result, for example, JSON, or XML.
   * @param currentUserName The current user name who sends request.
   * @param updatedType The type of connection action shown on the pop-up.
   * @return The detailed information of a user.
   * @throws Exception
   * @LevelAPI Platform
   * @anchor PeopleRestService.getPeopleInfo
   */
  @GET
  @Path("/getPeopleInfo/{userId}.{format}")
  @RolesAllowed("users")
  public Response getPeopleInfo(@Context UriInfo uriInfo,
                                @Context SecurityContext securityContext,
                                @PathParam("userId") String userId,
                                @PathParam("format") String format,
                                @QueryParam("currentUserName") String currentUserName,
                                @QueryParam("updatedType") String updatedType) throws Exception {
    //
    
    if (format.indexOf('.') > 0) {
      userId = new StringBuffer(userId).append(".").append(format.substring(0, format.lastIndexOf('.'))).toString();
      format = format.substring(format.lastIndexOf('.') + 1);
    }
    
    String[] mediaTypes = new String[] { "json", "xml" };
    format = ArrayUtils.contains(mediaTypes, format) ? format : mediaTypes[0];
    
    if(currentUserName == null || currentUserName.trim().isEmpty()) {
      currentUserName = getUserId(securityContext, uriInfo);
    }
    
    //
    MediaType mediaType = Util.getMediaType(format, mediaTypes);
    
    PeopleInfo peopleInfo = new PeopleInfo(NO_INFO);
    Identity identity = getIdentityManager()
        .getOrCreateIdentity(OrganizationIdentityProvider.NAME, userId, false);
    Identity currentIdentity = getIdentityManager()
        .getOrCreateIdentity(OrganizationIdentityProvider.NAME, currentUserName, false);
    if (identity != null) {
      // public information
      peopleInfo.setFullName(identity.getProfile().getFullName());
      peopleInfo.setPosition(StringEscapeUtils.unescapeHtml(identity.getProfile().getPosition()));
      peopleInfo.setDeleted(identity.isDeleted());
      Profile userProfile = identity.getProfile();
      String avatarURL = userProfile.getAvatarUrl();
      if (avatarURL == null) {
        avatarURL = LinkProvider.PROFILE_DEFAULT_AVATAR_URL;
      }
      peopleInfo.setAvatarURL(avatarURL);
      
      
      String userType = ConversationState.getCurrent().getIdentity().getUserId();
      boolean isAnonymous = IdentityConstants.ANONIM.equals(userType) 
          || securityContext.getUserPrincipal() == null || !userType.equals(currentIdentity.getRemoteId());
      
      if (!isAnonymous) { // private information
        peopleInfo.setProfileUrl(LinkProvider.getProfileUri(identity.getRemoteId()));
        
        peopleInfo.setRelationshipType(NO_ACTION);
        
        String relationshipType = null;
        
        if(currentUserName != null && !userId.equals(currentUserName)) {
          // Set relationship type
          Relationship relationship = getRelationshipManager().get(currentIdentity, identity);
          
          if(currentIdentity != null) {
            // Process action
            if (updatedType != null) {
              if (currentIdentity != null) {
                if (ACCEPT_ACTION.equals(updatedType)) { // Accept or Deny
                  getRelationshipManager().confirm(currentIdentity, identity);
                } else if (DENY_ACTION.equals(updatedType)) {
                  getRelationshipManager().deny(currentIdentity, identity);
                } else if (REVOKE_ACTION.equals(updatedType)) {
                  getRelationshipManager().deny(currentIdentity, identity);
                } else if (INVITE_ACTION.equals(updatedType)) {
                  getRelationshipManager().inviteToConnect(currentIdentity, identity);
                } else if (REMOVE_ACTION.equals(updatedType)) {
                  getRelationshipManager().delete(getRelationshipManager().get(currentIdentity, identity));
                }

                relationship = getRelationshipManager().get(currentIdentity, identity);
              }
            }
  
            relationshipType = getRelationshipType(relationship, currentIdentity);
            peopleInfo.setRelationshipType(relationshipType);
          }
        }
        
        if (CONFIRMED_STATUS.equals(relationshipType)) {
        
          // exposed if relationship type is confirmed (has connection with current logged in user)
          String activityTitle = getLatestActivityTitle(identity, currentIdentity);
          if (activityTitle != null) {
            peopleInfo.setActivityTitle(StringEscapeUtils.unescapeHtml(activityTitle));
          }
        }
      }
    }
    
    return Util.getResponse(peopleInfo, uriInfo, mediaType, Response.Status.OK);
  }

  public static class ConnectionInfoRestOut extends HashMap<String, Object> {

    private static final long serialVersionUID = -3638967656497819786L;

    public static enum Field {
      /**
       * User Displayname
       */
      DISPLAY_NAME("displayName"),
      /**
       * full url of avatar
       */
      AVATAR_URL("avatarURL"),
      /**
       * full url of profile
       */
      PROFILE_URL("profileURL"),
      /**
       * activity text
       */
      ACTIVITY_TITLE("activityTitle"),
      /**
       * activity text
       */
      ACTIVITY_ID("activityId"),
      /**
       * activity pretty posted time ( ago style )
       */
      PRETTY_POSTED_TIME("prettyPostedTime"),
      /** 
       * Identity's Position 
      */
      POSITION("position");
      
      
     /**
      * String type.
      */
      private final String fieldName;

     /**
      * private constructor.
      *
      * @param string string type
      */
      private Field(final String string) {
        fieldName = string;
      }
      
      public String toString() {
        return fieldName;
      }
    }
    /**
     * Default constructor, used by JAX-RS.
     */
    public ConnectionInfoRestOut() {
      initialize();
    }
    
    public ConnectionInfoRestOut(Identity identity) {
      this.setDisplayName(identity.getProfile().getFullName());
      this.setAvatarUrl(Util.buildAbsoluteAvatarURL(identity));
      this.setPosition(StringEscapeUtils.unescapeHtml(identity.getProfile().getPosition()));
    }
    
    public ConnectionInfoRestOut(Identity identity, Activity lastestActivity, String lang){
      this.setDisplayName(identity.getProfile().getFullName());
      this.setAvatarUrl(Util.buildAbsoluteAvatarURL(identity));
      this.setProfileUrl(identity.getProfile().getUrl());
      this.setActivityTitle(lastestActivity.getTitle());
      
      Calendar calendar = Calendar.getInstance();
      calendar.setLenient(false);
      int gmtoffset = calendar.get(Calendar.DST_OFFSET) + calendar.get(Calendar.ZONE_OFFSET);
      calendar.setTimeInMillis(lastestActivity.getPostedTime() - gmtoffset);
      this.setPrettyPostedTime(TimeConvertUtils.convertXTimeAgo(calendar.getTime(), "EEE,MMM dd,yyyy", new Locale(lang),
                                                                TimeConvertUtils.MONTH));
      
      this.setPosition(StringEscapeUtils.unescapeHtml(identity.getProfile().getPosition()));
      this.setActivityId(lastestActivity.getId());
    }
    
    public String getDisplayName() {
      return (String) this.get(Field.DISPLAY_NAME.toString());
    }

    public void setDisplayName(final String displayName) {
      if(displayName != null){
        this.put(Field.DISPLAY_NAME.toString(), displayName);
      } else {
        this.put(Field.DISPLAY_NAME.toString(), "");
      }
    }
    
    public String getAvatarUrl() {
      return (String) this.get(Field.AVATAR_URL.toString());
    }

    public void setAvatarUrl(final String avatarUrl) {
      if(avatarUrl != null){
        this.put(Field.AVATAR_URL.toString(), avatarUrl);
      } else {
        this.put(Field.AVATAR_URL.toString(), "");
      }
    }
    
    
    public String getProfileUrl() {
      return (String) this.get(Field.PROFILE_URL.toString());
    }

    public void setProfileUrl(final String profileUrl) {
      if(profileUrl != null){
        this.put(Field.PROFILE_URL.toString(), Util.getBaseUrl() + profileUrl);
      } else {
        this.put(Field.PROFILE_URL.toString(), "");
      }
    }
    
    public String getActivityTitle() {
      return (String) this.get(Field.ACTIVITY_TITLE.toString());
    }

    public void setActivityTitle(final String activityTitle) {
      if(activityTitle != null){
        this.put(Field.ACTIVITY_TITLE.toString(), activityTitle);
      } else {
        this.put(Field.ACTIVITY_TITLE.toString(), "");
      }
    }
    
    public String getPrettyPostedTime() {
      return  (String) this.get(Field.PRETTY_POSTED_TIME);
    }

    public void setPrettyPostedTime(final String postedTime) {
      if(postedTime != null){
        this.put(Field.PRETTY_POSTED_TIME.toString(), postedTime);
      } else {
        this.put(Field.PRETTY_POSTED_TIME.toString(), new Long(0));
      }
    }
    
    public String getPosition() {
      return  (String) this.get(Field.POSITION);
    }

    public void setPosition(final String position) {
      if(position != null){
        this.put(Field.POSITION.toString(), position);
      } else {
        this.put(Field.POSITION.toString(), "");
      }
    }
    
    public String getActivityId() {
      return  (String) this.get(Field.ACTIVITY_ID);
    }

    public void setActivityId(final String activityId) {
      if(activityId != null){
        this.put(Field.ACTIVITY_ID.toString(), activityId);
      } else {
        this.put(Field.ACTIVITY_ID.toString(), "");
      }
    }    
    private void initialize(){
      this.setActivityTitle("");
      this.setAvatarUrl("");
      this.setDisplayName("");
      this.setProfileUrl("");
      this.setActivityId("");
      this.setPosition("");
      this.setPrettyPostedTime("");
    }
  }
  
  private String getUserId(SecurityContext securityContext, UriInfo uriInfo) {
    String userId = StringUtils.EMPTY;
    try {
      userId = ConversationState.getCurrent().getIdentity().getUserId();
    } catch (Exception e) {
      LOG.debug("Could not get id of user from ConversationState.");  
    }
    
    if(userId == null || userId.isEmpty() || IdentityConstants.ANONIM.equals(userId)) {
      if (securityContext != null && securityContext.getUserPrincipal() != null) {
        return securityContext.getUserPrincipal().getName();
      } else if (uriInfo != null) {
        return Util.getViewerId(uriInfo);
      }
    }
    return userId;
  }

  private HashMap<String, Object> getIdentityInfo(Identity existingIdentity, String lang){
    RealtimeListAccess<ExoSocialActivity>  activityRealtimeListAccess = 
                                            activityManager.getActivitiesWithListAccess(existingIdentity);
    if(activityRealtimeListAccess.getSize() == 0 ){
      return null;
    }
    Activity lastestActivity =  activityRealtimeListAccess.load(0, 1)[0];
    return new ConnectionInfoRestOut(existingIdentity, lastestActivity, lang);
  }
  
  /**
   * Gets type of relationship appropriate to each specific relationship.
   * 
   * @param relationship Relationship of current user and selected user.
   * @param identity Current identity
   * @return Relationship Type.
   */
  private String getRelationshipType(Relationship relationship, Identity identity) {
    if (relationship == null) return ALIEN_STATUS;
    if (relationship.getStatus() == Relationship.Type.PENDING) {
      if (relationship.getSender().equals(identity)) {
        return WAITING_STATUS;  
      }
      return PENDING_STATUS;
    } else if (relationship.getStatus() == Relationship.Type.CONFIRMED) {
      return CONFIRMED_STATUS;
    } else if (relationship.getStatus() == Relationship.Type.IGNORED) {
      return IGNORED_STATUS;
    }
    
    return ALIEN_STATUS;
  }
  
  public SpaceService getSpaceService() {
    if (spaceService == null) {
      spaceService = (SpaceService) getPortalContainer().getComponentInstanceOfType(SpaceService.class);
    }
    return spaceService;
  }

  /**
   * Gets identityManager
   * @return
   */
  private IdentityManager getIdentityManager() {
    if (identityManager == null) {
      identityManager = (IdentityManager) getPortalContainer().getComponentInstanceOfType(IdentityManager.class);
    }
    return identityManager;
  }
  
  /**
   * Gets activity Manager instance.
   * @return activityManager
   * @see ActivityManager
   */
  private ActivityManager getActivityManager() {
    if (activityManager == null) {
      activityManager = (ActivityManager) getPortalContainer().getComponentInstanceOfType(ActivityManager.class);
    }
    return activityManager;
  }
  
  /**
   * Gets identityManager
   * @return
   */
  private RelationshipManager getRelationshipManager() {
    if (relationshipManager == null) {
      relationshipManager = (RelationshipManager) getPortalContainer().getComponentInstanceOfType(RelationshipManager.class);
    }
    return relationshipManager;
  }
  
  /**
   * Gets Portal Container instance.
   * @return portalContainer
   * @see PortalContainer
   */
  private ExoContainer getPortalContainer() {
    ExoContainer exoContainer = ExoContainerContext.getCurrentContainer();
    if (exoContainer == null) {
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    }
    return exoContainer;
  }
  
  static public class UserInfo {
    static private String AVATAR_URL = "/eXoSkin/skin/images/system/UserAvtDefault.png";

    String id;
    String name;
    String avatar;
    String type;

    public void setId(String id) {
      this.id = "@" + id;
    }

    public String getId() {
      return id;
    }

    public void setName(String name) {
      this.name = name;
    }

    public String getName() {
      return name;
    }

    public void setAvatar(String url) {
      this.avatar = url;
    }

    public String getAvatar() {
      if (avatar == null || avatar.length() == 0) return AVATAR_URL;
      return avatar;
    }

    public String getType() {
      return type;
    }

    public void setType(String type) {
      this.type = type;
    }

    @Override
    public int hashCode() {
      return id.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
      if(obj == null || !(obj instanceof UserInfo)) {
        return false;
      }
      return id.equals(((UserInfo)obj).getId());
    }
  }

  private String getLatestActivityTitle(Identity identity, Identity currentIdentity) {
    RealtimeListAccess<ExoSocialActivity> activitiesListAccess = getActivityManager()
        .getActivitiesByPoster(identity, DEFAULT_ACTIVITY, LINK_ACTIVITY, DOC_ACTIVITY);
    
    int totalActivities = activitiesListAccess.getSize();
    int loadedActivityNum = 0;
    while (true) {
      List<ExoSocialActivity> activities = activitiesListAccess.loadAsList(0, 20);
      
      loadedActivityNum += activities.size();
      
      for (ExoSocialActivity act : activities) {
        
        if (getIdentityManager().getOrCreateIdentity(
            OrganizationIdentityProvider.NAME, act.getStreamOwner(), false) != null) {
          return act.getTitle();
        }
        
        if (getIdentityManager().getOrCreateIdentity(
            SpaceIdentityProvider.NAME, act.getStreamOwner(), false) != null) {
          Space space = getSpaceService().getSpaceByPrettyName(act.getStreamOwner());
          if (getSpaceService().isMember(space, currentIdentity.getRemoteId())) {
            return act.getTitle();
          }
        }
      }
      
      if (loadedActivityNum >= totalActivities) {
        return null;  
      }
    }
  }
}