org.craftercms.profile.controllers.rest.ProfileController.java Source code

Java tutorial

Introduction

Here is the source code for org.craftercms.profile.controllers.rest.ProfileController.java

Source

/*
 * Copyright (C) 2007-2014 Crafter Software Corporation.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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.craftercms.profile.controllers.rest;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.craftercms.profile.api.Profile;
import org.craftercms.profile.api.SortOrder;
import org.craftercms.profile.api.VerificationToken;
import org.craftercms.profile.api.exceptions.ProfileException;
import org.craftercms.profile.api.services.ProfileAttachment;
import org.craftercms.profile.api.services.ProfileService;
import org.craftercms.profile.exceptions.NoSuchProfileException;
import org.craftercms.profile.exceptions.NoSuchVerificationTokenException;
import org.craftercms.profile.exceptions.ParamDeserializationException;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.multipart.MultipartFile;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiImplicitParam;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiParam;

import static org.craftercms.profile.api.ProfileConstants.*;

/**
 * REST controller for the profile service.
 *
 * @author avasquez
 */
@Controller
@RequestMapping(BASE_URL_PROFILE)
@Api(value = "profile", basePath = BASE_URL_PROFILE, description = "Profile operations")
public class ProfileController {

    private static final TypeReference<Map<String, Object>> ATTRIBUTES_TYPE_REFERENCE = new TypeReference<Map<String, Object>>() {
    };

    protected ProfileService profileService;
    protected ObjectMapper objectMapper;

    @Required
    public void setProfileService(ProfileService profileService) {
        this.profileService = profileService;
    }

    @Required
    public void setObjectMapper(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @ApiOperation("Creates a new profile for a specific tenant name")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_CREATE, method = RequestMethod.POST)
    @ResponseBody
    public Profile createProfile(
            @ApiParam("The name of the tenant to add the profile to") @RequestParam(PARAM_TENANT_NAME) String tenantName,
            @ApiParam("The profile's username") @RequestParam(PARAM_USERNAME) String username,
            @ApiParam("The profile's password") @RequestParam(value = PARAM_PASSWORD, required = false) String password,
            @ApiParam("The profile's email") @RequestParam(PARAM_EMAIL) String email,
            @ApiParam("If the profile should be enabled or not") @RequestParam(PARAM_ENABLED) boolean enabled,
            @ApiParam("The profile's roles") @RequestParam(value = PARAM_ROLE, required = false) Set<String> roles,
            @ApiParam("The additional attributes to add to the profile (specify a JSON string)") @RequestParam(value = PARAM_ATTRIBUTES, required = false) String serializedAttributes,
            @ApiParam("The URL (sans token) the user needs to go in case it needs to verify the created profile "
                    + "(verification depends on tenant)") @RequestParam(value = PARAM_VERIFICATION_URL, required = false) String verificationUrl)
            throws ProfileException {
        Map<String, Object> attributes = deserializeAttributes(serializedAttributes);

        return profileService.createProfile(tenantName, username, password, email, enabled, roles, attributes,
                verificationUrl);
    }

    @ApiOperation("Updates the profile's info")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_UPDATE, method = RequestMethod.POST)
    @ResponseBody
    public Profile updateProfile(@ApiParam("The profile's ID") @PathVariable(PATH_VAR_ID) String profileId,
            @ApiParam("The new username for the profile") @RequestParam(value = PARAM_USERNAME, required = false) String username,
            @ApiParam("The new password for the profile") @RequestParam(value = PARAM_PASSWORD, required = false) String password,
            @ApiParam("The new email for the profile") @RequestParam(value = PARAM_EMAIL, required = false) String email,
            @ApiParam("If the profile should be enabled or not") @RequestParam(value = PARAM_ENABLED, required = false) Boolean enabled,
            @ApiParam("The new roles for the profile") @RequestParam(value = PARAM_ROLE, required = false) Set<String> roles,
            @ApiParam("The attributes to update (specify a JSON string)") @RequestParam(value = PARAM_ATTRIBUTES, required = false) String serializedAttributes,
            @ApiParam("The name of the attributes to return (don't specify to return all)") @RequestParam(value = PARAM_ATTRIBUTE_TO_RETURN, required = false) String[] attributesToReturn)
            throws ProfileException {
        Map<String, Object> attributes = deserializeAttributes(serializedAttributes);

        return profileService.updateProfile(profileId, username, password, email, enabled, roles, attributes,
                attributesToReturn);
    }

    @ApiOperation("Enables a profile")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_ENABLE, method = RequestMethod.POST)
    @ResponseBody
    public Profile enableProfile(@ApiParam("The profile's ID") @PathVariable(PATH_VAR_ID) String profileId,
            @ApiParam("The name of the attributes to return (don't specify to return all)") @RequestParam(value = PARAM_ATTRIBUTE_TO_RETURN, required = false) String[] attributesToReturn)
            throws ProfileException {
        return profileService.enableProfile(profileId, attributesToReturn);
    }

    @ApiOperation("Disables a profile")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_DISABLE, method = RequestMethod.POST)
    @ResponseBody
    public Profile disableProfile(@ApiParam("The profile's ID") @PathVariable(PATH_VAR_ID) String profileId,
            @ApiParam("The name of the attributes to return (don't specify to return all)") @RequestParam(value = PARAM_ATTRIBUTE_TO_RETURN, required = false) String[] attributesToReturn)
            throws ProfileException {
        return profileService.disableProfile(profileId, attributesToReturn);
    }

    @ApiOperation("Assigns roles to a profile")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_ADD_ROLES, method = RequestMethod.POST)
    @ResponseBody
    public Profile addRoles(@ApiParam("The profile's ID") @PathVariable(PATH_VAR_ID) String profileId,
            @ApiParam("The roles to assign") @RequestParam(PARAM_ROLE) Collection<String> roles,
            @ApiParam("The name of the attributes to return (don't specify to return all)") @RequestParam(value = PARAM_ATTRIBUTE_TO_RETURN, required = false) String[] attributesToReturn)
            throws ProfileException {
        return profileService.addRoles(profileId, roles, attributesToReturn);
    }

    @ApiOperation("Removes assigned roles from a profile")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_REMOVE_ROLES, method = RequestMethod.POST)
    @ResponseBody
    public Profile removeRoles(@ApiParam("The profile's ID") @PathVariable(PATH_VAR_ID) String profileId,
            @ApiParam("The roles to remove") @RequestParam(PARAM_ROLE) Collection<String> roles,
            @ApiParam("The name of the attributes to return (don't specify to return all)") @RequestParam(value = PARAM_ATTRIBUTE_TO_RETURN, required = false) String[] attributesToReturn)
            throws ProfileException {
        return profileService.removeRoles(profileId, roles, attributesToReturn);
    }

    @ApiOperation("Sets the profile as verified if the verification token is valid")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_VERIFY, method = RequestMethod.POST)
    @ResponseBody
    public Profile verifyProfile(
            @ApiParam("The verification token ID") @RequestParam(PARAM_VERIFICATION_TOKEN_ID) String verificationTokenId,
            @ApiParam("The name of the attributes to return (don't specify to return all)") @RequestParam(value = PARAM_ATTRIBUTE_TO_RETURN, required = false) String[] attributesToReturn)
            throws ProfileException {
        return profileService.verifyProfile(verificationTokenId, attributesToReturn);
    }

    @ApiOperation("Returns the attributes of a profile")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_GET_ATTRIBUTES, method = RequestMethod.GET)
    @ResponseBody
    public Map<String, Object> getAttributes(
            @ApiParam("The profile's ID") @PathVariable(PATH_VAR_ID) String profileId,
            @ApiParam("The name of the attributes to return (don't specify to return all)") @RequestParam(value = PARAM_ATTRIBUTE_TO_RETURN, required = false) String[] attributesToReturn)
            throws ProfileException {
        return profileService.getAttributes(profileId, attributesToReturn);
    }

    @ApiOperation(value = "Updates the attributes of a profile", notes = "The specified attributes will be merged "
            + "with existing attributes")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_UPDATE_ATTRIBUTES, method = RequestMethod.POST)
    @ResponseBody
    public Profile updateAttributes(@ApiParam("The profile's ID") @PathVariable(PATH_VAR_ID) String profileId,
            @ApiParam("The new attributes") @RequestBody Map<String, Object> attributes,
            @ApiParam("The name of the attributes to return (don't specify to return all)") @RequestParam(value = PARAM_ATTRIBUTE_TO_RETURN, required = false) String[] attributesToReturn)
            throws ProfileException {
        return profileService.updateAttributes(profileId, attributes, attributesToReturn);
    }

    @ApiOperation("Removes a list of attributes of a profile")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_REMOVE_ATTRIBUTES, method = RequestMethod.POST)
    @ResponseBody
    public Profile removeAttributes(@ApiParam("The profile's ID") @PathVariable(PATH_VAR_ID) String profileId,
            @ApiParam("The name of the attributes to remove") @RequestParam(PARAM_ATTRIBUTE_NAME) Collection<String> attributeNames,
            @ApiParam("The name of the attributes to return (don't specify to return all)") @RequestParam(value = PARAM_ATTRIBUTE_TO_RETURN, required = false) String[] attributesToReturn)
            throws ProfileException {
        return profileService.removeAttributes(profileId, attributeNames, attributesToReturn);
    }

    @ApiOperation("Deletes a profile")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_DELETE_PROFILE, method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.OK)
    public void deleteProfile(@ApiParam("The profile's ID") @PathVariable(PATH_VAR_ID) String profileId)
            throws ProfileException {
        profileService.deleteProfile(profileId);
    }

    @ApiOperation("Returns the single profile that matches the specified query")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_GET_ONE_BY_QUERY, method = RequestMethod.GET)
    @ResponseBody
    public Profile getProfileByQuery(
            @ApiParam("The tenant's name") @RequestParam(PARAM_TENANT_NAME) String tenantName,
            @ApiParam("The Mongo query used to search for the profiles. Must not contain "
                    + "the $where operator, the tenant's name (already specified) or any "
                    + "non-readable attribute by the application") @RequestParam(PARAM_QUERY) String query,
            @ApiParam("The name of the attributes to return (don't specify to return all)") @RequestParam(value = PARAM_ATTRIBUTE_TO_RETURN, required = false) String[] attributesToReturn)
            throws ProfileException {
        Profile profile = profileService.getProfileByQuery(tenantName, query, attributesToReturn);
        if (profile != null) {
            return profile;
        } else {
            throw new NoSuchProfileException.ByQuery(tenantName, query);
        }
    }

    @ApiOperation("Returns the profile for the specified ID")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_GET, method = RequestMethod.GET)
    @ResponseBody
    public Profile getProfile(@ApiParam("The profile's ID") @PathVariable(PATH_VAR_ID) String profileId,
            @ApiParam("The name of the attributes to return (don't specify to return all)") @RequestParam(value = PARAM_ATTRIBUTE_TO_RETURN, required = false) String[] attributesToReturn)
            throws ProfileException {
        Profile profile = profileService.getProfile(profileId, attributesToReturn);
        if (profile != null) {
            return profile;
        } else {
            throw new NoSuchProfileException.ById(profileId);
        }
    }

    @ApiOperation("Returns the user for the specified tenant and username")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_GET_BY_USERNAME, method = RequestMethod.GET)
    @ResponseBody
    public Profile getProfileByUsername(
            @ApiParam("The tenant's name") @RequestParam(PARAM_TENANT_NAME) String tenantName,
            @ApiParam("The profile's username") @RequestParam(PARAM_USERNAME) String username,
            @ApiParam("The name of the attributes to return (don't specify to return all)") @RequestParam(value = PARAM_ATTRIBUTE_TO_RETURN, required = false) String[] attributesToReturn)
            throws ProfileException {
        Profile profile = profileService.getProfileByUsername(tenantName, username, attributesToReturn);
        if (profile != null) {
            return profile;
        } else {
            throw new NoSuchProfileException.ByUsername(tenantName, username);
        }
    }

    @ApiOperation("Returns the profile for the specified ticket")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_GET_BY_TICKET, method = RequestMethod.GET)
    @ResponseBody
    public Profile getProfileByTicket(
            @ApiParam("The ID ticket of the authenticated profile") @RequestParam(PARAM_TICKET_ID) String ticketId,
            @ApiParam("The name of the attributes to return (don't specify to return all)") @RequestParam(value = PARAM_ATTRIBUTE_TO_RETURN, required = false) String[] attributesToReturn)
            throws ProfileException {
        Profile profile = profileService.getProfileByTicket(ticketId, attributesToReturn);
        if (profile != null) {
            return profile;
        } else {
            throw new NoSuchProfileException.ByTicket(ticketId);
        }
    }

    @ApiOperation("Returns the number of profiles of the specified tenant")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_GET_COUNT, method = RequestMethod.GET)
    @ResponseBody
    public long getProfileCount(@ApiParam("The tenant's name") @RequestParam(PARAM_TENANT_NAME) String tenantName)
            throws ProfileException {
        return profileService.getProfileCount(tenantName);
    }

    @ApiOperation("Returns the number of profiles that match the query for the specified tenant")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_TENANT_COUNT_BY_QUERY, method = RequestMethod.GET)
    @ResponseBody
    public long getProfileCount(@ApiParam("The tenant's name") @RequestParam(PARAM_TENANT_NAME) String tenantName,
            @ApiParam("The Mongo query used to search for the profiles. Must not contain the $where operator, "
                    + "the tenant's name (already specified) or any non-readable attribute by the application") @RequestParam(PARAM_QUERY) String query)
            throws ProfileException {
        return profileService.getProfileCountByQuery(tenantName, query);
    }

    @ApiOperation("Returns the profiles that match the specified query")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_GET_BY_QUERY, method = RequestMethod.GET)
    @ResponseBody
    public List<Profile> getProfilesByQuery(
            @ApiParam("The tenant's name") @RequestParam(PARAM_TENANT_NAME) String tenantName,
            @ApiParam("The Mongo query used to search for the profiles. Must not contain the $where operator, the tenant's name "
                    + "(already specified) or any non-readable attribute by the application") @RequestParam(PARAM_QUERY) String query,
            @ApiParam("Profile attribute to sort the list by") @RequestParam(value = PARAM_SORT_BY, required = false) String sortBy,
            @ApiParam("The sort order (either ASC or DESC)") @RequestParam(value = PARAM_SORT_ORDER, required = false) SortOrder sortOrder,
            @ApiParam("From the entire list of results, the position where the "
                    + "actual results should start (useful for pagination)") @RequestParam(value = PARAM_START, required = false) Integer start,
            @ApiParam("The number of profiles to return") @RequestParam(value = PARAM_COUNT, required = false) Integer count,
            @ApiParam("The name of the attributes to return (don't specify to "
                    + "return all)") @RequestParam(value = PARAM_ATTRIBUTE_TO_RETURN, required = false) String[] attributesToReturn)
            throws ProfileException {
        List<Profile> profiles = profileService.getProfilesByQuery(tenantName, query, sortBy, sortOrder, start,
                count, attributesToReturn);
        if (profiles != null) {
            return profiles;
        } else {
            return Collections.emptyList();
        }
    }

    @ApiOperation("Returns a list of profiles for the specified list of IDs")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_GET_BY_IDS, method = RequestMethod.GET)
    @ResponseBody
    public List<Profile> getProfileByIds(
            @ApiParam("The IDs of the profiles to look for") @RequestParam(PATH_VAR_ID) List<String> profileIds,
            @ApiParam("Profile attribute to sort the list by") @RequestParam(value = PARAM_SORT_BY, required = false) String sortBy,
            @ApiParam("The sort order (either ASC or DESC)") @RequestParam(value = PARAM_SORT_ORDER, required = false) SortOrder sortOrder,
            @ApiParam("The name of the attributes to return (don't specify to "
                    + "return all)") @RequestParam(value = PARAM_ATTRIBUTE_TO_RETURN, required = false) String[] attributesToReturn)
            throws ProfileException {
        List<Profile> profiles = profileService.getProfilesByIds(profileIds, sortBy, sortOrder, attributesToReturn);
        if (profiles != null) {
            return profiles;
        } else {
            return Collections.emptyList();
        }
    }

    @ApiOperation("Returns a range of profiles for the specified tenant")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_GET_RANGE, method = RequestMethod.GET)
    @ResponseBody
    public List<Profile> getProfileRange(
            @ApiParam("The tenant's name") @RequestParam(PARAM_TENANT_NAME) String tenantName,
            @ApiParam("Profile attribute to sort the list by") @RequestParam(value = PARAM_SORT_BY, required = false) String sortBy,
            @ApiParam("The sort order (either ASC or DESC)") @RequestParam(value = PARAM_SORT_ORDER, required = false) SortOrder sortOrder,
            @ApiParam("From the entire list of results, the position where the actual results should start (useful for pagination)") @RequestParam(value = PARAM_START, required = false) Integer start,
            @ApiParam("The number of profiles to return") @RequestParam(value = PARAM_COUNT, required = false) Integer count,
            @ApiParam("The name of the attributes to return (don't specify to "
                    + "return all)") @RequestParam(value = PARAM_ATTRIBUTE_TO_RETURN, required = false) String[] attributesToReturn)
            throws ProfileException {
        List<Profile> profiles = profileService.getProfileRange(tenantName, sortBy, sortOrder, start, count,
                attributesToReturn);
        if (profiles != null) {
            return profiles;
        } else {
            return Collections.emptyList();
        }
    }

    @ApiOperation("Returns a list of profiles for a specific role and tenant")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_GET_BY_ROLE, method = RequestMethod.GET)
    @ResponseBody
    public List<Profile> getProfilesByRole(
            @ApiParam("The tenant's name") @RequestParam(PARAM_TENANT_NAME) String tenantName,
            @ApiParam("The role's name") @RequestParam(PARAM_ROLE) String role,
            @ApiParam("Profile attribute to sort the list by") @RequestParam(value = PARAM_SORT_BY, required = false) String sortBy,
            @ApiParam("The sort order (either ASC or DESC)") @RequestParam(value = PARAM_SORT_ORDER, required = false) SortOrder sortOrder,
            @ApiParam("The name of the attributes to return (don't specify to "
                    + "return all)") @RequestParam(value = PARAM_ATTRIBUTE_TO_RETURN, required = false) String[] attributesToReturn)
            throws ProfileException {
        List<Profile> profiles = profileService.getProfilesByRole(tenantName, role, sortBy, sortOrder,
                attributesToReturn);
        if (profiles != null) {
            return profiles;
        } else {
            return Collections.emptyList();
        }
    }

    @ApiOperation("Returns the list of profiles that have the given attribute, with any value")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_GET_BY_EXISTING_ATTRIB, method = RequestMethod.GET)
    @ResponseBody
    public List<Profile> getProfilesByExistingAttribute(
            @ApiParam("The tenant's name") @RequestParam(PARAM_TENANT_NAME) String tenantName,
            @ApiParam("The name of the attribute profiles must have") @RequestParam(PARAM_ATTRIBUTE_NAME) String attributeName,
            @ApiParam("Profile attribute to sort the list by") @RequestParam(value = PARAM_SORT_BY, required = false) String sortBy,
            @ApiParam("The sort order (either ASC or DESC)") @RequestParam(value = PARAM_SORT_ORDER, required = false) SortOrder sortOrder,
            @ApiParam("The name of the attributes to return (don't "
                    + "specify to return all)") @RequestParam(value = PARAM_ATTRIBUTE_TO_RETURN, required = false) String[] attributesToReturn)
            throws ProfileException {
        List<Profile> profiles = profileService.getProfilesByExistingAttribute(tenantName, attributeName, sortBy,
                sortOrder, attributesToReturn);
        if (profiles != null) {
            return profiles;
        } else {
            return Collections.emptyList();
        }
    }

    @ApiOperation("Returns the list of profiles that have the given attribute with the given value")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_GET_BY_ATTRIB_VALUE, method = RequestMethod.GET)
    @ResponseBody
    public List<Profile> getProfilesByAttributeValue(
            @ApiParam("The tenant's name") @RequestParam(PARAM_TENANT_NAME) String tenantName,
            @ApiParam("The name of the attribute profiles must have") @RequestParam(PARAM_ATTRIBUTE_NAME) String attributeName,
            @ApiParam("The value of the attribute profiles must have") @RequestParam(PARAM_ATTRIBUTE_VALUE) String attributeValue,
            @ApiParam("Profile attribute to sort the list by") @RequestParam(value = PARAM_SORT_BY, required = false) String sortBy,
            @ApiParam("The sort order (either ASC or DESC)") @RequestParam(value = PARAM_SORT_ORDER, required = false) SortOrder sortOrder,
            @ApiParam("The name of the attributes to return (don't "
                    + "specify to return all)") @RequestParam(value = PARAM_ATTRIBUTE_TO_RETURN, required = false) String[] attributesToReturn)
            throws ProfileException {
        List<Profile> profiles = profileService.getProfilesByAttributeValue(tenantName, attributeName,
                attributeValue, sortBy, sortOrder, attributesToReturn);
        if (profiles != null) {
            return profiles;
        } else {
            return Collections.emptyList();
        }
    }

    @ApiOperation("Sends an email to the profile's user to indicate that the password needs to be reset")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_RESET_PASSWORD, method = RequestMethod.POST)
    @ResponseBody
    public Profile resetPassword(@ApiParam("The profile's ID") @PathVariable(PATH_VAR_ID) String profileId,
            @ApiParam("The base URL to use to build the final URL the profile will use to reset their password.") @RequestParam(PARAM_RESET_PASSWORD_URL) String resetPasswordUrl,
            @ApiParam("The name of the attributes to return (don't specify to return all)") @RequestParam(value = PARAM_ATTRIBUTE_TO_RETURN, required = false) String[] attributesToReturn)
            throws ProfileException {
        return profileService.resetPassword(profileId, resetPasswordUrl, attributesToReturn);
    }

    @ApiOperation("Resets a profile's password")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_CHANGE_PASSWORD, method = RequestMethod.POST)
    @ResponseBody
    public Profile changePassword(
            @ApiParam("The reset token ID") @RequestParam(PARAM_RESET_TOKEN_ID) String resetTokenId,
            @ApiParam("The new password") @RequestParam(PARAM_NEW_PASSWORD) String newPassword,
            @ApiParam("The name of the attributes to return (don't specify to return all)") @RequestParam(value = PARAM_ATTRIBUTE_TO_RETURN, required = false) String[] attributesToReturn)
            throws ProfileException {
        return profileService.changePassword(resetTokenId, newPassword, attributesToReturn);
    }

    @ApiOperation(value = "Creates a token that can be sent to the user in an email as a link", notes = "After the user clicks the link, the token then can be passed to verifyProfile or "
            + "changePassword to verify that the user agrees")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_CREATE_VERIFICATION_TOKEN, method = RequestMethod.POST)
    @ResponseBody
    public VerificationToken createVerificationToken(
            @ApiParam("The profile ID of the user that needs to be contacted") @PathVariable(PATH_VAR_ID) String profileId)
            throws ProfileException {
        return profileService.createVerificationToken(profileId);
    }

    @ApiOperation(value = "Returns the verification token that corresponds to the given ID")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_GET_VERIFICATION_TOKEN, method = RequestMethod.GET)
    @ResponseBody
    public VerificationToken getVerificationToken(
            @ApiParam("The token ID") @PathVariable(PATH_VAR_ID) String tokenId) throws ProfileException {
        VerificationToken token = profileService.getVerificationToken(tokenId);
        if (token != null) {
            return token;
        } else {
            throw new NoSuchVerificationTokenException(tokenId);
        }
    }

    @ApiOperation(value = "Deletes a verification token when it's not needed anymore", notes = "Not necessary to call if verifyProfile or changePassword, since they already delete the "
            + "token")
    @ApiImplicitParam(name = "accessTokenId", required = true, dataType = "string", paramType = "query", value = "The ID of the application access token")
    @RequestMapping(value = URL_PROFILE_DELETE_VERIFICATION_TOKEN, method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.OK)
    public void deleteVerificationToken(
            @ApiParam("The ID of the token to delete") @PathVariable(PATH_VAR_ID) String tokenId)
            throws ProfileException {
        profileService.deleteVerificationToken(tokenId);
    }

    @ResponseBody
    @RequestMapping(value = URL_PROFILE_UPLOAD_ATTACHMENT, method = RequestMethod.POST)
    @ApiOperation(value = "Upload a attachment to the current profile.", notes = "If the mime type of the attachment is not on the valid "
            + "list will fail")
    @ApiImplicitParam(name = "attachment", required = true, dataType = "file", paramType = "file", value = "File to be uploaded")
    public ProfileAttachment uploadProfileAttachment(
            @ApiParam("The profile's ID") @PathVariable(PATH_VAR_ID) String profileId,
            @RequestParam(name = PARAM_FILENAME, required = false) String filename, MultipartFile attachment)
            throws ProfileException {
        Profile profile = profileService.getProfile(profileId);
        if (profile != null) {
            String attachmentName = StringUtils.isNotBlank(filename) ? filename : attachment.getOriginalFilename();
            try {
                return profileService.addProfileAttachment(profile.getId().toString(), attachmentName,
                        attachment.getInputStream());
            } catch (IOException e) {
                throw new ProfileException("Unable to upload Attachment", e);
            }
        } else {
            throw new NoSuchProfileException.ById(profileId);
        }
    }

    @RequestMapping(value = URL_PROFILE_GET_ATTACHMENTS, method = RequestMethod.GET)
    @ResponseBody
    @ApiOperation(value = "Gets all attachments for the given Profile", notes = "If profile does not exist")
    public List<ProfileAttachment> getAttachments(
            @ApiParam("The profile's ID") @PathVariable(PATH_VAR_ID) String profileId)
            throws ProfileException, IOException {
        Profile profile = profileService.getProfile(profileId);
        if (profile != null) {
            return profileService.getProfileAttachments(profile.getId().toString());
        } else {
            throw new NoSuchProfileException.ById(profileId);
        }
    }

    @RequestMapping(value = URL_PROFILE_GET_ATTACHMENTS_DETAILS, method = RequestMethod.GET)
    @ResponseBody
    @ApiOperation(value = "Gets all attachments for the given Profile", notes = "If profile does not exist")
    public ProfileAttachment getAttachmentDetails(
            @ApiParam("The profile's ID") @PathVariable(PATH_VAR_ID) String profileId,
            @ApiParam("Attachment Id to get") @PathVariable(PATH_VAR_ATTACHMENT) String attachmentId)
            throws ProfileException, IOException {
        Profile profile = profileService.getProfile(profileId);
        if (profile != null) {
            return profileService.getProfileAttachmentInformation(profile.getId().toString(), attachmentId);
        } else {
            throw new NoSuchProfileException.ById(profileId);
        }
    }

    @RequestMapping(value = URL_PROFILE_GET_ATTACHMENT, method = RequestMethod.GET)
    @ApiOperation(value = "Gets the requested attachment of the given profile", notes = "If Attachment or profile does not exist will "
            + "throw error, content-type,content-legnth headers" + " are set")
    public void getAttachment(@ApiParam("The profile's ID") @PathVariable(PATH_VAR_ID) String profileId,
            @ApiParam("Attachment Id to get") @PathVariable(PATH_VAR_ATTACHMENT) String attachmentId,
            HttpServletResponse response) throws ProfileException, IOException {
        Profile profile = profileService.getProfile(profileId);
        if (profile != null) {
            InputStream input = null;
            try {
                input = profileService.getProfileAttachment(attachmentId, profile.getId().toString());
                if (input != null) {
                    ProfileAttachment attachment = profileService
                            .getProfileAttachmentInformation(profile.getId().toString(), attachmentId);

                    response.setContentType(attachment.getContentType());
                    response.setContentLength((int) attachment.getFileSizeBytes());

                    IOUtils.copy(input, response.getOutputStream());
                }
            } catch (ProfileException ex) {
                response.sendError(HttpServletResponse.SC_NOT_FOUND);
                response.setContentLength(0);
            } finally {
                if (input != null) {
                    input.close();
                }
            }
        } else {
            throw new NoSuchProfileException.ById(profileId);
        }
    }

    protected Map<String, Object> deserializeAttributes(String serializedAttributes)
            throws ParamDeserializationException {
        Map<String, Object> attributes = null;

        if (StringUtils.isNotEmpty(serializedAttributes)) {
            try {
                attributes = objectMapper.readValue(serializedAttributes, ATTRIBUTES_TYPE_REFERENCE);
            } catch (IOException e) {
                throw new ParamDeserializationException(e);
            }
        }

        return attributes;
    }

}