Util.java

/*
 * Copyright (C) 2003-2011 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.exoplatform.social.service.rest;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;

import org.exoplatform.commons.utils.CommonsUtils;
import org.exoplatform.container.ExoContainerContext;
import org.exoplatform.container.PortalContainer;
import org.exoplatform.services.rest.impl.EnvironmentContext;
import org.exoplatform.services.security.ConversationState;
import org.exoplatform.services.security.IdentityConstants;
import org.exoplatform.social.core.activity.model.ActivityStream;
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.service.LinkProvider;
import org.exoplatform.social.core.space.spi.SpaceService;
import org.exoplatform.social.service.rest.api.models.IdentityRestOut;
import org.exoplatform.social.service.rest.api.models.ProfileRestOut;

/**
 * Util.java: utility class for rest <br>.
 *
 * @author hoatle
 * @since  Jan 5, 2009
 */
public final class Util {
  
  private static final Pattern URL_PATTERN = Pattern
      .compile("^(?i)" +
      "(" +
        "((?:(?:ht)tp(?:s?)\\:\\/\\/)?" +                                                       // protolcol
        "(?:\\w+:\\w+@)?" +                                                                       // username password
        "(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\." +  // IPAddress
        "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|" +     // IPAddress
        "(?:(?:[-\\p{L}\\p{Digit}\\+\\$\\-\\*\\=]+\\.)+" +
        "(?:[a-z]{2,}))))|" +                                                                     //Domain
        "(?:(?:(?:ht)tp(?:s?)\\:\\/\\/)(?:\\w+:\\w+@)?(?:[-\\p{L}\\p{Digit}\\+\\$\\-\\*\\=]+))" + // Protocol with hostname
      ")" +
      "(?::[\\d]{1,5})?" +                                                                        // port
      "(?:[\\/|\\?|\\#].*)?$");                                                               // path and query

  /**
   * Prevents constructing a new instance.
   */
  private Util() {
  }

  /**
   * Checks a url is in a valid form or not.
   * 
   * @param link
   * @return
   */
  public static boolean isValidURL(String link) {
    if (link == null || link.length() == 0) return false;
    return URL_PATTERN.matcher(link).matches();
  }
  
  /**
   * Gets the response object constructed from the provided params.
   *
   * @param entity the identity
   * @param uriInfo the uri request info
   * @param mediaType the media type to be returned
   * @param status the status code
   * @return response the response object
   */
  public static Response getResponse(Object entity, UriInfo uriInfo, MediaType mediaType, Response.Status status) {
    Response resp = Response.created(uriInfo.getAbsolutePath())
                   .entity(entity)
                   .type(mediaType.toString() + "; charset=utf-8")
                   .status(status)
                   .build();
    
    return resp;
  }
  /**
   * Gets mediaType from string format.
   * Currently supports json and xml only.
   *
   * @param format
   * @return mediaType of matched or throw BAD_REQUEST exception
   * @throws WebApplicationException
   * @deprecated User {@link #getMediaType(String, String[])} instead.
   *             Will be removed by 1.3.x
   */
  @Deprecated
  public static MediaType getMediaType(String format) throws WebApplicationException {
    if (format.equals("json")) {
      return MediaType.APPLICATION_JSON_TYPE;
    } else if(format.equals("xml")) {
      return MediaType.APPLICATION_XML_TYPE;
    }
    throw new WebApplicationException(Response.Status.BAD_REQUEST);
  }


  /**
   * Gets the media type from an expected format string (usually the input) and an array of supported format strings.
   * If expectedFormat is not found in the supported format array, Status.UNSUPPORTED_MEDIA_TYPE is thrown.
   * The supported format must include one of those format: json, xml, atom or rss, otherwise Status.NOT_ACCEPTABLE
   * could be thrown.
   *
   * @param expectedFormat the expected input format
   * @param supportedFormats the supported format array
   * @return the associated media type
   */
  public static MediaType getMediaType(String expectedFormat, String[] supportedFormats) {

    if (!isSupportedFormat(expectedFormat, supportedFormats)) {
      throw new WebApplicationException(Status.UNSUPPORTED_MEDIA_TYPE);
    }

    if (expectedFormat.equals("json") && isSupportedFormat("json", supportedFormats)) {
      return MediaType.APPLICATION_JSON_TYPE;
    } else if (expectedFormat.equals("xml") && isSupportedFormat("xml", supportedFormats)) {
      return MediaType.APPLICATION_XML_TYPE;
    } else if (expectedFormat.equals("atom") && isSupportedFormat("atom", supportedFormats)) {
      return MediaType.APPLICATION_ATOM_XML_TYPE;
    }
    //TODO What's about RSS format?
    throw new WebApplicationException(Status.NOT_ACCEPTABLE);
  }

  
  /**
   * Get viewerId from servlet request data information provided by OpenSocial signed request.
   *  
   * @param uriInfo
   * @return
   */
  public static String getViewerId (UriInfo uriInfo) {
    String viewerId = null;

    URI uri = uriInfo.getRequestUri();
    String requestString = uri.getQuery();
    if (requestString != null) {
      String[] queryParts = requestString.split("&");
    
      for (String queryPart : queryParts) {
        if (queryPart.startsWith("opensocial_viewer_id")) {
          viewerId = queryPart.substring(queryPart.indexOf("=") + 1, queryPart.length());
          break;
        }
      }
    }
    
    if (viewerId == null) {
      ConversationState state = ConversationState.getCurrent();
      if (state != null) {
        viewerId = state.getIdentity().getUserId();
      }
    }

    return viewerId;
  }

  /**
   * Gets identity of viewer user (logged-in user). Do not load profile.
   *
   * @return identity
   * @since 1.2.0 GA
   * @deprecated Use {@link #getViewerIdentity(String, String)} instead.
   *             Will be removed by 1.3.x
   */
  @Deprecated
  public static Identity getViewerIdentity(String viewerId) {
    return getUserIdentity(viewerId, false);
  }

  /**
   * Gets identity of viewer user (logged-in user). Do not load profile.
   *
   * @return identity
   * @since 1.2.3
   */
  public static Identity getViewerIdentity(String portalContainerName, String viewerId) {
    return getUserIdentity(portalContainerName, viewerId, false);
  }

  /**
   * Gets identity from the remote id (user name)
   * 
   * @param userName
   * @param loadProfile
   * @return identity
   * @since 1.2.0 GA
   * @deprecated Use {@link #getUserIdentity(String, String, boolean)} instead.
   *             Will be removed by 1.3.x
   */
  @Deprecated
  public static Identity getUserIdentity(String userName, boolean loadProfile) {
    return getIdentityManager().getOrCreateIdentity(OrganizationIdentityProvider.NAME, userName, loadProfile);
  }

  /**
   * Gets identity from the remote id (user name)
   *
   * @param portalContainerName
   * @param userName
   * @param loadProfile
   * @return identity
   * @since 1.2.3
   */
  public static Identity getUserIdentity(String portalContainerName, String userName, boolean loadProfile) {
    return getIdentityManager(portalContainerName).
           getOrCreateIdentity(OrganizationIdentityProvider.NAME, userName, loadProfile);
  }
  
  /**
   * Gets identityManager with default portal container.
   *
   * @return identityManager
   * @since 1.2.0 GA
   * @deprecated Use {@link #getIdentityManager(String)} instead.
   *             Will be removed by 1.3.x
   */
  @Deprecated
  public static final IdentityManager getIdentityManager() {
    return (IdentityManager) getDefaultPortalContainer().getComponentInstanceOfType(IdentityManager.class);
  }

  /**
   * Gets {@link IdentityManager} with specified portal container name.
   *
   * @param portalContainerName the specified portal container name
   * @return the identity manager
   * @since  1.2.0-GA
   */
  public static final IdentityManager getIdentityManager(String portalContainerName) {
    return (IdentityManager) getPortalContainerByName(portalContainerName).
                             getComponentInstanceOfType(IdentityManager.class);
  }


  /**
   * Gets {@link SpaceService} with default portal container.
   *
   * @return the space service
   * @since  1.2.0-GA
   * @deprecated Use {@link #getSpaceService(String)} instead.
   *             Will be removed by 1.3.x
   */
  @Deprecated
  public static final SpaceService getSpaceService() {
    return (SpaceService) getDefaultPortalContainer().getComponentInstanceOfType(SpaceService.class);
  }

  /**
   * Gets {@link SpaceService} with specified portal container name.
   *
   * @param portalContainerName the specified portal container name
   * @return the space service
   * @since  1.2.0-GA
   */
  public static final SpaceService getSpaceService(String portalContainerName) {
    return (SpaceService) getPortalContainerByName(portalContainerName).getComponentInstanceOfType(SpaceService.class);
  }


  /**
   * Gets {@link ActivityManager} with default portal container.
   *
   * @return the activity manager
   * @since  1.2.0-GA
   * @deprecated Use {@link #getActivityManager(String)} instead.
   *             Will be removed by 1.3.x
   */
  @Deprecated
  public static final ActivityManager getActivityManager() {
    return (ActivityManager) getDefaultPortalContainer().getComponentInstanceOfType(ActivityManager.class);
  }

  /**
   * Gets {@link ActivityManager} with specified portal container name.
   *
   * @param portalContainerName the specified portal container
   * @return the activity manager
   * @since  1.2.0-GA
   */
  public static final ActivityManager getActivityManager(String portalContainerName) {
    return (ActivityManager) getPortalContainerByName(portalContainerName).
                             getComponentInstanceOfType(ActivityManager.class);
  }

  /**
   * Gets {@link RelationshipManager} with default portal container.
   *
   * @return the relationship manager
   * @since  1.2.0-GA
   * @deprecated Use {@link #getRelationshipManager(String)} instead.
   *             Will be removed by 1.3.x
   */
  @Deprecated
  public static final RelationshipManager getRelationshipManager() {
    return (RelationshipManager) getDefaultPortalContainer().getComponentInstanceOfType(RelationshipManager.class);
  }


  /**
   * Gets {@link RelationshipManager} with specified portal container name.
   *
   * @param portalContainerName the specified portal container name
   * @return the relationship manager
   * @since  1.2.0-GA
   */
  public static final RelationshipManager getRelationshipManager(String portalContainerName) {
    return (RelationshipManager) getPortalContainerByName(portalContainerName).
                                 getComponentInstanceOfType(RelationshipManager.class);
  }

  /**
   * Gets a portal container by its name.
   *
   * @param portalContainerName the specified portal container name
   * @return the portal container name
   * @since  1.2.3
   */
  public static final PortalContainer getPortalContainerByName(String portalContainerName) {
    return (PortalContainer) ExoContainerContext.getContainerByName(portalContainerName);
  }


  /**
   * Converts a timestamp string to time string by the pattern: EEE MMM d HH:mm:ss Z yyyy.
   *
   * @param timestamp the timestamp to convert
   * @return the time string
   */
  public static final String convertTimestampToTimeString(long timestamp) {
   SimpleDateFormat dateFormat = new SimpleDateFormat("EEE MMM d HH:mm:ss Z yyyy");
   dateFormat.setTimeZone(TimeZone.getDefault());
   return dateFormat.format(new Date(timestamp));
  }

  /**
   * Gets a owner identity Id from a provided activity.
   *
   * @param activity the activity to gets its owner identity
   * @return the owner identity
   * @since  1.2.3
   * @deprecated Use {@link #getOwnerIdentityIdFromActivity(String, ExoSocialActivity)} instead.
   *             Will be removed by 1.3.x
   */
  @Deprecated
  public static Identity getOwnerIdentityIdFromActivity(ExoSocialActivity activity) {
    IdentityManager identityManager = getIdentityManager();
    ActivityStream activityStream=  activity.getActivityStream();
    ActivityStream.Type activityType =  activityStream.getType();
    String name = activity.getStreamOwner();
    if(activityType.equals(ActivityStream.Type.USER)){
      return identityManager.getOrCreateIdentity(OrganizationIdentityProvider.NAME, name, false);
    } else {
      return identityManager.getOrCreateIdentity(SpaceIdentityProvider.NAME, name, false);
    }
  }

  /**
   * Gets a owner identity Id from a provided activity.
   *
   * @param portalContainerName the portal container name
   * @param activity the activity to gets its owner identity
   * @return the owner identity
   * @since  1.2.0-GA
   */
  public static Identity getOwnerIdentityIdFromActivity(String portalContainerName, ExoSocialActivity activity) {
    IdentityManager identityManager = getIdentityManager(portalContainerName);
    ActivityStream activityStream=  activity.getActivityStream();
    ActivityStream.Type activityType =  activityStream.getType();
    String name = activity.getStreamOwner();
    if(activityType.equals(ActivityStream.Type.USER)){
      return identityManager.getOrCreateIdentity(OrganizationIdentityProvider.NAME, name, false);
    } else {
      return identityManager.getOrCreateIdentity(SpaceIdentityProvider.NAME, name, false);
    }
  }


  /**
   * Gets base url (host + post) from a context uriInfo.
   * <br>
   * For example: http://localhost:8080
   *
   * @return the base url (host + port)
   * @author <a href="http://hoatle">hoatle (hoatlevan at gmail dot com)</a>
   * @since  1.2.3
   *
   */
  public static final String getBaseUrl() {
    HttpServletRequest currentServletRequest = getCurrentServletRequest();
    //always returns the port, even the request has no explicit port => avatarUrl always return with 80 port.
    //need to improve this: if no port is specified => the return link should have no port specified as
    // 80 is the default port.
    return currentServletRequest.getScheme() + "://" + currentServletRequest.getServerName() +
                                                 ":" + currentServletRequest.getServerPort();
  }


  /**
   * Gets current http servlet request provided by Rest Service Framework.
   *
   * @return the current http servlet request
   */
  public static HttpServletRequest getCurrentServletRequest() {
    EnvironmentContext environmentContext = EnvironmentContext.getCurrent();
    return (HttpServletRequest) environmentContext.get(HttpServletRequest.class);
  }

  /**
   * Checks if an expected format is supported not not.
   *
   * @param expectedFormat  the expected format
   * @param supportedFormats the array of supported format
   * @return true or false
   */
  private static boolean isSupportedFormat(String expectedFormat, String[] supportedFormats) {
    for (String supportedFormat : supportedFormats) {
      if (supportedFormat.equals(expectedFormat)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Gets default portal container name.
   *
   * @return the portal container
   */
  private static PortalContainer getDefaultPortalContainer() {
    return PortalContainer.getInstance();
  }
  
  /**
   * Build absolute AvatarURL from in IndentityRestOut if avatar == null or "" use default avatar base on Identity's type
   * @param resultIdentity
   */
  
  public static void buildAbsoluteAvatarURL(IdentityRestOut resultIdentity){
    if(resultIdentity.getProfile() != null && 
        resultIdentity.getProviderId() != null){
      ProfileRestOut resultProfile =  resultIdentity.getProfile();
      if(resultProfile.getAvatarUrl() == null || resultProfile.getAvatarUrl().trim().equals("") ){
        if(resultIdentity.getProviderId().
            equals(SpaceIdentityProvider.NAME)){
          resultProfile.setAvatarUrl(getBaseUrl() + LinkProvider.SPACE_DEFAULT_AVATAR_URL);
        } else {
          resultProfile.setAvatarUrl(getBaseUrl() + LinkProvider.PROFILE_DEFAULT_AVATAR_URL);
        }
      } else {
        if(!resultProfile.getAvatarUrl().startsWith("http://") && !resultProfile.getAvatarUrl().startsWith("https://")){
          resultProfile.setAvatarUrl(getBaseUrl() + resultProfile.getAvatarUrl());
        }
      }
    }
  }
  
  /**
   * Build absolute AvatarURL from in IndentityRestOut if avatar == null or "" use default avatar base on Identity's type
   * @param resultIdentity
   */
  
  public static String buildAbsoluteAvatarURL(Identity resultIdentity){
    if(resultIdentity.getProfile() != null && 
        resultIdentity.getProviderId() != null){
      Profile resultProfile =  resultIdentity.getProfile();
      if(resultProfile.getAvatarUrl() == null || resultProfile.getAvatarUrl().trim().equals("") ){
        if(resultIdentity.getProviderId().
            equals(SpaceIdentityProvider.NAME)){
          return getBaseUrl() + LinkProvider.SPACE_DEFAULT_AVATAR_URL;
        } else {
          return getBaseUrl() + LinkProvider.PROFILE_DEFAULT_AVATAR_URL;
        }
      } else {
          return getBaseUrl() + resultProfile.getAvatarUrl();
      }
    } else {
      return null;
    }
    
  }
  
  /**
   * Gets UserIdentity of authenticated user.
   * @param portalContainerName current portal container name
   * @return Identity of user, if not authenticated return null
   */
  public static Identity getAuthenticatedUserIdentity(String portalContainerName) {
    if(ConversationState.getCurrent()!=null && ConversationState.getCurrent().getIdentity() != null &&
              ConversationState.getCurrent().getIdentity().getUserId() != null){
      IdentityManager identityManager =  Util.getIdentityManager(portalContainerName);
      String authenticatedUserRemoteID = ConversationState.getCurrent().getIdentity().getUserId(); 
      return identityManager.getOrCreateIdentity(OrganizationIdentityProvider.NAME, authenticatedUserRemoteID, false);
    } else {
      return null;
    }
  }
  
  /**
   * Check if identityId contained in liked Identity, if yes return true else return false.
   * @param identityID
   * @param activity
   * @return
   */
  public static boolean isLikedByIdentity(String identityID, ExoSocialActivity activity){
    String[] likedIdentityIds = activity.getLikeIdentityIds();
    if(activity.getLikeIdentityIds()!=null && likedIdentityIds.length > 0 ){
      for (int i = 0; i < likedIdentityIds.length; i++) {
        if (identityID.equals(likedIdentityIds[i])){
          return true;
        }
      }
    }
    return false;
  }
  
  /**
   * Try to guess the mime type of url using the Content-Type from header, the extension of filename or some bytes of content.
   * This method can be wrong if server don't provide the Content-Type, wrong or unknown file extension. So use it as your risk.
   * @param urlString
   * @return
   * @since 1.2.7
   */
  public static String getMimeTypeOfURL(String urlString){
    URLConnection urlConnection = null;
    try {
      String mimeType = null;
      URL url = new URL(urlString); 
      urlConnection = url.openConnection();
      mimeType = urlConnection.getContentType();
      if(mimeType != null){
        return mimeType;
      }
      mimeType = URLConnection.guessContentTypeFromName(urlString);
      if(mimeType != null){
        return mimeType;
      }
      mimeType = URLConnection.guessContentTypeFromStream(urlConnection.getInputStream());
      if(mimeType != null){
        return mimeType;
      }
      return "";
    } catch (MalformedURLException e) {
      return "";
    } catch (IOException e) {
      return "";
    } finally {
      urlConnection = null;
    }
  }
  
  /**
   * Decode query parameters of string URL
   * Example:
   * - Input: {@code http://google.com?%3Cscript%3E}
   * - Output: {@code http://google.com?<script>}
   *
   * @param url The string URL to decode
   * @return The URL decoded query parameters
   * @since 4.1.0
   */
  public static String getDecodeQueryURL(String url) {
    if (isValidURL(url)) {
      String query;
      try {
        query = new URL(url).getQuery();
        if (query != null) {
          String newQuery = URLDecoder.decode(query, "UTF-8");
          return url.replace(query, newQuery);
        }
      } catch (Exception e) {
        return url;
      }
    }
    return url;
  }
  
  /**
   * Checks if user is mentioned or not.
   * 
   * @param existingActivity Activity to check.
   * @param identityId Identity Id to check mentioned or not.
   * 
   * @return true if input user has been mentioned in activity.
   */
  public static boolean hasMentioned(ExoSocialActivity existingActivity, String identityId) {
    for (String mentioner : existingActivity.getMentionedIds()) {
      if (mentioner.startsWith(identityId)) { // identityId@mentioned_times
        return true;
      }
    }
    return false;
  }
  
  /** 
   * Get base url of rest service
   * 
   * @param type the type of rest service
   * @param id the id of object
   * 
   * @return base rest url like : http://localhost:8080/rest/v1/social/users/123456
   */
  public static String getRestUrl(String type, String id, String restPath) {
    String version = restPath.split("/")[1]; // path /v1/social/identities
    String socialResource = restPath.split("/")[2]; // path /v1/social/identities
    
    return new StringBuffer(getBaseRestUrl())
    .append("/").append(version)
    .append("/").append(socialResource)
    .append("/").append(type)
    .append("/").append(id).toString();
  }
  
  /** 
   * Get base url of rest service
   * 
   * @return base rest url like : http://localhost:8080/rest
   */
  public static String getBaseRestUrl() {
    return new StringBuffer(CommonsUtils.getCurrentDomain()).append("/").append(CommonsUtils.getRestContextName()).toString();
  }
  
  /**
   * Get the rest url to load all members or managers of a space
   * 
   * @param id the id of space
   * @param returnManager return managers or members
   * @return rest url to load all members or managers of a space
   */
  public static String getMembersSpaceRestUrl(String id, boolean returnManager, String restPath) {
    StringBuffer spaceMembersRestUrl = new StringBuffer(getRestUrl(RestUtils.SPACES_TYPE, id, restPath)).append("/").append(RestUtils.USERS_TYPE);
    if (returnManager) {
      return spaceMembersRestUrl.append("?role=manager").toString();
    }
    return spaceMembersRestUrl.toString();
  }
  
  /**
   * Get the rest url in order to load all comments of an activity
   * 
   * @param activityId activity's id
   * @return
   */
  public static String getCommentsActivityRestUrl(String activityId, String restPath) {
    return new StringBuffer(getRestUrl(RestUtils.ACTIVITIES_TYPE, activityId, restPath)).append("/").append("comments").toString();
  }
  
  /**
   * Check if the current user is authenticated
   * 
   * @return true if user not authenticated
   */
  public static boolean isAnonymous() {
    return IdentityConstants.ANONIM.equals(ConversationState.getCurrent().getIdentity().getUserId());
  }
}