/* * 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.services.impl; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.collections4.MapUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.NotImplementedException; import org.craftercms.commons.rest.RestClientUtils; import org.craftercms.profile.api.Profile; import org.craftercms.profile.api.ProfileConstants; import org.craftercms.profile.api.SortOrder; import org.craftercms.profile.api.VerificationToken; import org.craftercms.profile.api.exceptions.I10nProfileException; import org.craftercms.profile.api.exceptions.ProfileException; import org.craftercms.profile.api.services.ProfileAttachment; import org.craftercms.profile.api.services.ProfileService; import org.springframework.beans.factory.annotation.Required; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.io.PathResource; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import static org.craftercms.profile.api.ProfileConstants.*; /** * REST client implementation of {@link org.craftercms.profile.api.services.ProfileService}. * * @author avasquez */ public class ProfileServiceRestClient extends AbstractProfileRestClientBase implements ProfileService { public static final ParameterizedTypeReference<List<Profile>> profileListTypeRef = new ParameterizedTypeReference<List<Profile>>() {}; public static final ParameterizedTypeReference<byte[]> byteArrayTypeRef = new ParameterizedTypeReference<byte[]>() { }; public static final ParameterizedTypeReference<List<ProfileAttachment>> profileAttachmentListTypeRef = new ParameterizedTypeReference<List<ProfileAttachment>>() { }; public static final String ERROR_KEY_ATTRIBUTES_SERIALIZATION_ERROR = "profile.client.attributes.serializationError"; public static final String ERROR_KEY_INVALID_URI_ERROR = "profile.client.invalidUri"; private ObjectMapper objectMapper; @Required public void setObjectMapper(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @Override public Profile createProfile(String tenantName, String username, String password, String email, boolean enabled, Set<String> roles, Map<String, Object> attributes, String verificationUrl) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValue(PARAM_TENANT_NAME, tenantName, params); RestClientUtils.addValue(PARAM_USERNAME, username, params); RestClientUtils.addValue(PARAM_PASSWORD, password, params); RestClientUtils.addValue(PARAM_EMAIL, email, params); RestClientUtils.addValue(PARAM_ENABLED, enabled, params); RestClientUtils.addValues(PARAM_ROLE, roles, params); if (MapUtils.isNotEmpty(attributes)) { RestClientUtils.addValue(PARAM_ATTRIBUTES, serializeAttributes(attributes), params); } RestClientUtils.addValue(PARAM_VERIFICATION_URL, verificationUrl, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_CREATE); return doPostForObject(url, params, Profile.class); } @Override public Profile updateProfile(String profileId, String username, String password, String email, Boolean enabled, Set<String> roles, Map<String, Object> attributes, String... attributesToReturn) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValue(PARAM_USERNAME, username, params); RestClientUtils.addValue(PARAM_PASSWORD, password, params); RestClientUtils.addValue(PARAM_EMAIL, email, params); RestClientUtils.addValue(PARAM_ENABLED, enabled, params); // Send empty role to indicate that all roles should be deleted if (roles != null && roles.isEmpty()) { RestClientUtils.addValue(PARAM_ROLE, "", params); } else { RestClientUtils.addValues(PARAM_ROLE, roles, params); } if (MapUtils.isNotEmpty(attributes)) { RestClientUtils.addValue(PARAM_ATTRIBUTES, serializeAttributes(attributes), params); } RestClientUtils.addValues(PARAM_ATTRIBUTE_TO_RETURN, attributesToReturn, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_UPDATE); return doPostForObject(url, params, Profile.class, profileId); } @Override public Profile verifyProfile(String verificationTokenId, String... attributesToReturn) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValue(PARAM_VERIFICATION_TOKEN_ID, verificationTokenId, params); RestClientUtils.addValues(PARAM_ATTRIBUTE_TO_RETURN, attributesToReturn, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_VERIFY); return doPostForObject(url, params, Profile.class); } @Override public Profile enableProfile(String profileId, String... attributesToReturn) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValues(PARAM_ATTRIBUTE_TO_RETURN, attributesToReturn, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_ENABLE); return doPostForObject(url, params, Profile.class, profileId); } @Override public Profile disableProfile(String profileId, String... attributesToReturn) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValues(PARAM_ATTRIBUTE_TO_RETURN, attributesToReturn, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_DISABLE); return doPostForObject(url, params, Profile.class, profileId); } @Override public Profile addRoles(String profileId, Collection<String> roles, String... attributesToReturn) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValues(PARAM_ROLE, roles, params); RestClientUtils.addValues(PARAM_ATTRIBUTE_TO_RETURN, attributesToReturn, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_ADD_ROLES); return doPostForObject(url, params, Profile.class, profileId); } @Override public Profile removeRoles(String profileId, Collection<String> roles, String... attributesToReturn) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValues(PARAM_ROLE, roles, params); RestClientUtils.addValues(PARAM_ATTRIBUTE_TO_RETURN, attributesToReturn, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_REMOVE_ROLES); return doPostForObject(url, params, Profile.class, profileId); } @Override public Map<String, Object> getAttributes(String profileId, String... attributesToReturn) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValues(PARAM_ATTRIBUTE_TO_RETURN, attributesToReturn, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_GET_ATTRIBUTES); url = RestClientUtils.addQueryParams(url, params, false); return doGetForObject(url, Map.class, profileId); } @Override public Profile updateAttributes(String profileId, Map<String, Object> attributes, String... attributesToReturn) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValues(PARAM_ATTRIBUTE_TO_RETURN, attributesToReturn, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_UPDATE_ATTRIBUTES); url = RestClientUtils.addQueryParams(url, params, false); return doPostForObject(url, attributes, Profile.class, profileId); } @Override public Profile removeAttributes(String profileId, Collection<String> attributeNames, String... attributesToReturn) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValues(PARAM_ATTRIBUTE_NAME, attributeNames, params); RestClientUtils.addValues(PARAM_ATTRIBUTE_TO_RETURN, attributesToReturn, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_REMOVE_ATTRIBUTES); return doPostForObject(url, params, Profile.class, profileId); } @Override public void deleteProfile(String profileId) throws ProfileException { String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_DELETE_PROFILE); doPostForLocation(url, createBaseParams(), profileId); } @Override public Profile getProfileByQuery(String tenantName, String query, String... attributesToReturn) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValue(PARAM_TENANT_NAME, tenantName, params); RestClientUtils.addValue(PARAM_QUERY, query, params); RestClientUtils.addValues(PARAM_ATTRIBUTE_TO_RETURN, attributesToReturn, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_GET_ONE_BY_QUERY); url = RestClientUtils.addQueryParams(url, params, true); try { return doGetForObject(new URI(url), Profile.class); } catch (URISyntaxException e) { throw new I10nProfileException(ERROR_KEY_INVALID_URI_ERROR, url); } } @Override public Profile getProfile(String profileId, String... attributesToReturn) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValues(PARAM_ATTRIBUTE_TO_RETURN, attributesToReturn, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_GET); url = RestClientUtils.addQueryParams(url, params, false); return doGetForObject(url, Profile.class, profileId); } @Override public Profile getProfileByUsername(String tenantName, String username, String... attributesToReturn) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValue(PARAM_TENANT_NAME, tenantName, params); RestClientUtils.addValue(PARAM_USERNAME, username, params); RestClientUtils.addValues(PARAM_ATTRIBUTE_TO_RETURN, attributesToReturn, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_GET_BY_USERNAME); url = RestClientUtils.addQueryParams(url, params, false); return doGetForObject(url, Profile.class); } @Override public Profile getProfileByTicket(String ticketId, String... attributesToReturn) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValue(PARAM_TICKET_ID, ticketId, params); RestClientUtils.addValues(PARAM_ATTRIBUTE_TO_RETURN, attributesToReturn, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_GET_BY_TICKET); url = RestClientUtils.addQueryParams(url, params, false); return doGetForObject(url, Profile.class); } @Override public long getProfileCount(String tenantName) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValue(PARAM_TENANT_NAME, tenantName, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_GET_COUNT); url = RestClientUtils.addQueryParams(url, params, false); return doGetForObject(url, Long.class); } @Override public long getProfileCountByQuery(String tenantName, String query) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValue(PARAM_TENANT_NAME, tenantName, params); RestClientUtils.addValue(PARAM_QUERY, query, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_TENANT_COUNT_BY_QUERY); url = RestClientUtils.addQueryParams(url, params, true); try { return doGetForObject(new URI(url), Long.class); } catch (URISyntaxException e) { throw new I10nProfileException(ERROR_KEY_INVALID_URI_ERROR, url); } } @Override public List<Profile> getProfilesByQuery(String tenantName, String query, String sortBy, SortOrder sortOrder, Integer start, Integer count, String... attributesToReturn) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValue(PARAM_TENANT_NAME, tenantName, params); RestClientUtils.addValue(PARAM_QUERY, query, params); RestClientUtils.addValue(PARAM_SORT_BY, sortBy, params); RestClientUtils.addValue(PARAM_SORT_ORDER, sortOrder, params); RestClientUtils.addValue(PARAM_START, start, params); RestClientUtils.addValue(PARAM_COUNT, count, params); RestClientUtils.addValues(PARAM_ATTRIBUTE_TO_RETURN, attributesToReturn, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_GET_BY_QUERY); url = RestClientUtils.addQueryParams(url, params, true); try { return doGetForObject(new URI(url), profileListTypeRef); } catch (URISyntaxException e) { throw new I10nProfileException(ERROR_KEY_INVALID_URI_ERROR, url); } } @Override public List<Profile> getProfilesByIds(List<String> profileIds, String sortBy, SortOrder sortOrder, String... attributesToReturn) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValues(PARAM_ID, profileIds, params); RestClientUtils.addValue(PARAM_SORT_BY, sortBy, params); RestClientUtils.addValue(PARAM_SORT_ORDER, sortOrder, params); RestClientUtils.addValues(PARAM_ATTRIBUTE_TO_RETURN, attributesToReturn, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_GET_BY_IDS); url = RestClientUtils.addQueryParams(url, params, false); return doGetForObject(url, profileListTypeRef); } @Override public List<Profile> getProfileRange(String tenantName, String sortBy, SortOrder sortOrder, Integer start, Integer count, String... attributesToReturn) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValue(PARAM_TENANT_NAME, tenantName, params); RestClientUtils.addValue(PARAM_SORT_BY, sortBy, params); RestClientUtils.addValue(PARAM_SORT_ORDER, sortOrder, params); RestClientUtils.addValue(PARAM_START, start, params); RestClientUtils.addValue(PARAM_COUNT, count, params); RestClientUtils.addValues(PARAM_ATTRIBUTE_TO_RETURN, attributesToReturn, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_GET_RANGE); url = RestClientUtils.addQueryParams(url, params, false); return doGetForObject(url, profileListTypeRef); } @Override public List<Profile> getProfilesByRole(String tenantName, String role, String sortBy, SortOrder sortOrder, String... attributesToReturn) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValue(PARAM_TENANT_NAME, tenantName, params); RestClientUtils.addValue(PARAM_ROLE, role, params); RestClientUtils.addValue(PARAM_SORT_BY, sortBy, params); RestClientUtils.addValue(PARAM_SORT_ORDER, sortOrder, params); RestClientUtils.addValues(PARAM_ATTRIBUTE_TO_RETURN, attributesToReturn, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_GET_BY_ROLE); url = RestClientUtils.addQueryParams(url, params, false); return doGetForObject(url, profileListTypeRef); } @Override public List<Profile> getProfilesByExistingAttribute(String tenantName, String attributeName, String sortBy, SortOrder sortOrder, String... attributesToReturn) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValue(PARAM_TENANT_NAME, tenantName, params); RestClientUtils.addValue(PARAM_ATTRIBUTE_NAME, attributeName, params); RestClientUtils.addValue(PARAM_SORT_BY, sortBy, params); RestClientUtils.addValue(PARAM_SORT_ORDER, sortOrder, params); RestClientUtils.addValues(PARAM_ATTRIBUTE_TO_RETURN, attributesToReturn, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_GET_BY_EXISTING_ATTRIB); url = RestClientUtils.addQueryParams(url, params, false); return doGetForObject(url, profileListTypeRef); } @Override public List<Profile> getProfilesByAttributeValue(String tenantName, String attributeName, String attributeValue, String sortBy, SortOrder sortOrder, String... attributesToReturn) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValue(PARAM_TENANT_NAME, tenantName, params); RestClientUtils.addValue(PARAM_ATTRIBUTE_NAME, attributeName, params); RestClientUtils.addValue(PARAM_ATTRIBUTE_VALUE, attributeValue, params); RestClientUtils.addValue(PARAM_SORT_BY, sortBy, params); RestClientUtils.addValue(PARAM_SORT_ORDER, sortOrder, params); RestClientUtils.addValues(PARAM_ATTRIBUTE_TO_RETURN, attributesToReturn, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_GET_BY_ATTRIB_VALUE); url = RestClientUtils.addQueryParams(url, params, false); return doGetForObject(url, profileListTypeRef); } @Override public Profile resetPassword(String profileId, String resetPasswordUrl, String... attributesToReturn) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValue(PARAM_RESET_PASSWORD_URL, resetPasswordUrl, params); RestClientUtils.addValues(PARAM_ATTRIBUTE_TO_RETURN, attributesToReturn, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_RESET_PASSWORD); return doPostForObject(url, params, Profile.class, profileId); } @Override public Profile changePassword(String resetTokenId, String newPassword, String... attributesToReturn) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); RestClientUtils.addValue(PARAM_RESET_TOKEN_ID, resetTokenId, params); RestClientUtils.addValue(PARAM_NEW_PASSWORD, newPassword, params); RestClientUtils.addValues(PARAM_ATTRIBUTE_TO_RETURN, attributesToReturn, params); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_CHANGE_PASSWORD); return doPostForObject(url, params, Profile.class); } @Override public VerificationToken createVerificationToken(final String profileId) throws ProfileException { String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_CREATE_VERIFICATION_TOKEN); return doPostForObject(url, createBaseParams(), VerificationToken.class, profileId); } @Override public VerificationToken getVerificationToken(String tokenId) throws ProfileException { String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_GET_VERIFICATION_TOKEN); url = RestClientUtils.addQueryParams(url, createBaseParams(), false); return doGetForObject(url, VerificationToken.class, tokenId); } @Override public void deleteVerificationToken(String tokenId) throws ProfileException { String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_DELETE_VERIFICATION_TOKEN); doPostForLocation(url, createBaseParams(), tokenId); } @Override public ProfileAttachment addProfileAttachment(final String profileId, final String attachmentName, final InputStream file) throws ProfileException { MultiValueMap<String, Object> params = new LinkedMultiValueMap<>(); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_UPLOAD_ATTACHMENT); params.add(ProfileConstants.PARAM_ACCESS_TOKEN_ID, accessTokenIdResolver.getAccessTokenId()); Path dirTmp = null; File tmp = null; ProfileAttachment toReturn; try { dirTmp = Files.createTempDirectory(FileUtils.getTempDirectory().toPath(), profileId); tmp = new File(dirTmp.toFile(), attachmentName); FileUtils.copyInputStreamToFile(file, new File(dirTmp.toFile(), attachmentName)); params.add("attachment", new PathResource(tmp.toPath())); toReturn = doPostForUpload(url, params, ProfileAttachment.class, profileId); } catch (IOException e) { throw new ProfileException("Unable to upload file", e); } finally { try { file.close(); if (tmp != null) { tmp.delete(); } if (dirTmp != null) { tmp.delete(); } } catch (Throwable e) { //Possible unable to delete due folder not empty (should Happen) } } return toReturn; } @Override public ProfileAttachment getProfileAttachmentInformation(final String profileId, final String attachmentId) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_GET_ATTACHMENTS_DETAILS); url = RestClientUtils.addQueryParams(url, params, false); return doGetForObject(url, ProfileAttachment.class, profileId, attachmentId); } @Override public InputStream getProfileAttachment(final String attachmentId, final String profileId) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_GET_ATTACHMENT); url = RestClientUtils.addQueryParams(url, params, false); return new ByteArrayInputStream(doGetForObject(url, byteArrayTypeRef, profileId, attachmentId)); } @Override public List<ProfileAttachment> getProfileAttachments(final String profileId) throws ProfileException { MultiValueMap<String, String> params = createBaseParams(); String url = getAbsoluteUrl(BASE_URL_PROFILE + URL_PROFILE_GET_ATTACHMENTS); url = RestClientUtils.addQueryParams(url, params, false); return doGetForObject(url, profileAttachmentListTypeRef, profileId); } protected String serializeAttributes(Map<String, Object> attributes) throws ProfileException { try { return objectMapper.writeValueAsString(attributes); } catch (Exception e) { throw new I10nProfileException(ERROR_KEY_ATTRIBUTES_SERIALIZATION_ERROR, e); } } @Override public Profile setLastFailedLogin(final String profileId, final Date lastFailedLogin, final String... attributesToReturn) throws ProfileException { throw new NotImplementedException("This call is not intended to be call by external clients"); } @Override public Profile setFailedLoginAttempts(final String profileId, final int failedLoginAttempts, final String... attributesToReturn) throws ProfileException { throw new NotImplementedException("This call is not intended to be call by external clients"); } }