package org.xcolab.client.members; import org.xcolab.client.members.exceptions.LockoutLoginException; import org.xcolab.client.members.exceptions.MemberCategoryNotFoundException; import org.xcolab.client.members.exceptions.MemberNotFoundException; import org.xcolab.client.members.exceptions.PasswordLoginException; import org.xcolab.client.members.exceptions.UncheckedMemberNotFoundException; import org.xcolab.client.members.pojo.LoginBean; import org.xcolab.client.members.pojo.LoginLog; import org.xcolab.client.members.pojo.LoginToken; import org.xcolab.client.members.pojo.Member; import org.xcolab.client.members.pojo.MemberCategory; import org.xcolab.client.members.pojo.Role_; import org.xcolab.client.members.pojo.TokenValidity; import org.xcolab.util.clients.CoLabService; import org.xcolab.util.exceptions.InternalException; import org.xcolab.util.http.ServiceRequestUtils; import org.xcolab.util.http.caching.CacheKeys; import org.xcolab.util.http.caching.CacheName; import org.xcolab.util.http.client.RestResource; import org.xcolab.util.http.client.RestResource1; import org.xcolab.util.http.client.RestResource2L; import org.xcolab.util.http.client.RestService; import org.xcolab.util.http.client.queries.ListQuery; import org.xcolab.util.http.exceptions.EntityNotFoundException; import org.xcolab.util.http.exceptions.Http401UnauthorizedException; import org.xcolab.util.http.exceptions.Http403ForbiddenException; import org.xcolab.util.http.exceptions.UncheckedEntityNotFoundException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.List; public final class MembersClient { private static final RestService memberService = new RestService(CoLabService.MEMBER, ServiceRequestUtils.getNamespace()); private static final RestResource1<Member, Long> memberResource = new RestResource1<>(memberService, "members", Member.TYPES); private static final RestResource2L<Member, Role_> memberRoleResource = new RestResource2L<>(memberResource, "roles", Role_.TYPES); private static final RestResource<MemberCategory, Long> memberCategoryResource = new RestResource1<>(memberService, "membercategories", MemberCategory.TYPES); private static final RestResource<LoginLog, Long> loginLogResource = new RestResource1<>(memberService, "loginLogs", LoginLog.TYPES); private static final RestResource<LoginToken, String> loginTokenResource = new RestResource1<>( memberService, "loginTokens", LoginToken.TYPES); private MembersClient() { } public static List<Member> findMembersMatching(String partialName, int maxMembers) { return memberResource.list() .addRange(0, maxMembers) .queryParam("partialName", partialName) .queryParam("sort", "screenName") .execute(); } public static List<Member> listAllMembers() { return listMembers(null, null, null, null, true, 0, Integer.MAX_VALUE); } public static List<Member> listMembers(String categoryFilterValue, String screenNameFilterValue, String emailFilterValue, String sortField, boolean ascOrder, int firstMember, int lastMember) { final ListQuery<Member> memberListQuery = memberResource.list() .addRange(firstMember, lastMember) .optionalQueryParam("partialName", screenNameFilterValue) .optionalQueryParam("partialEmail", emailFilterValue) .optionalQueryParam("roleName", categoryFilterValue); if (sortField != null && !sortField.isEmpty()) { final String prefix = (ascOrder) ? ("") : ("-"); switch (sortField) { case "USER_NAME": memberListQuery.queryParam("sort", prefix + "screenName"); break; case "MEMBER_SINCE": memberListQuery.queryParam("sort", prefix + "createDate"); break; case "CATEGORY": memberListQuery.queryParam("sort", prefix + "roleName"); break; case "ACTIVITY": memberListQuery.queryParam("sort", prefix + "activityCount"); break; case "POINTS": memberListQuery.queryParam("sort", prefix + "points"); break; default: } } return memberListQuery.execute(); } public static Integer countMembers(String categoryFilterValue, String screenNameFilterValue) { try { return memberResource.<Member, Integer>service("count", Integer.class) .optionalQueryParam("screenName", screenNameFilterValue) .optionalQueryParam("category", categoryFilterValue) .withCache(CacheKeys.withClass(Member.class) .withParameter("screenName", screenNameFilterValue) .withParameter("category", categoryFilterValue).asCount(), CacheName.MISC_SHORT) .getChecked(); } catch (EntityNotFoundException e) { return 0; } } public static Integer getMemberActivityCount(long memberId) { try { return memberResource.service(memberId, "activityCount", Integer.class).getChecked(); } catch (EntityNotFoundException e) { return 0; } } public static Integer getMemberMaterializedPoints(long memberId) { try { return memberResource.service(memberId, "points", Integer.class).getChecked(); } catch (EntityNotFoundException e) { return 0; } } public static Integer getMemberHypotheticalPoints(long memberId) { try { return memberResource.service(memberId, "points", Integer.class) .queryParam("hypothetical", true) .getChecked(); } catch (EntityNotFoundException e) { return 0; } } public static List<Role_> getMemberRoles(long memberId) { return memberRoleResource.resolveParent(memberResource.id(memberId)) .list() .withCache(CacheName.ROLES) .execute(); } public static void assignMemberRole(long memberId, long roleId) { memberRoleResource.resolveParent(memberResource.id(memberId)) .update(null, roleId) .execute(); ServiceRequestUtils.clearCache(CacheName.ROLES); } public static void removeMemberRole(long memberId, long roleId) { memberRoleResource.resolveParent(memberResource.id(memberId)) .delete(roleId) .execute(); ServiceRequestUtils.clearCache(CacheName.ROLES); } //TODO: this seems to be duplicated in the ContestTeamMemberClient public static List<Role_> getMemberRolesInContest(long memberId, long contestId) { return memberRoleResource.resolveParent(memberResource.id(memberId)) .list() .queryParam("contestId", contestId) .withCache(CacheName.CONTEST_DETAILS) .execute(); } public static List<MemberCategory> listMemberCategories() { return memberCategoryResource.list() .withCache(CacheName.ROLES) .execute(); } public static MemberCategory getMemberCategory(long roleId) { try { return memberCategoryResource.get(roleId) .withCache(CacheName.ROLES).executeChecked(); } catch (EntityNotFoundException e) { throw new MemberCategoryNotFoundException("Category with role id " + roleId + " not found."); } } public static MemberCategory getMemberCategory(String displayName) { MemberCategory memberCategory = memberCategoryResource.list() .queryParam("displayName", displayName) .withCache(CacheName.ROLES) .executeWithResult().getFirstIfExists(); if (memberCategory == null) { throw new MemberCategoryNotFoundException("Category with name " + displayName + " not found."); } return memberCategory; } public static List<MemberCategory> getVisibleMemberCategories() { return memberCategoryResource.list() .queryParam("showInList", true) .withCache(CacheName.ROLES) .execute(); } public static Member getMember(long memberId) throws MemberNotFoundException { try { return getMemberInternal(memberId); } catch (EntityNotFoundException e) { throw new MemberNotFoundException("Member with id " + memberId + " not found."); } } public static Member getMemberOrNull(long memberId) { try { return getMemberInternal(memberId); } catch (EntityNotFoundException e) { return null; } } public static Member getMemberUnchecked(long memberId) { try { return getMemberInternal(memberId); } catch (EntityNotFoundException e) { throw new UncheckedMemberNotFoundException(memberId); } } private static Member getMemberInternal(long memberId) throws EntityNotFoundException { return memberResource.get(memberId) .withCache(CacheName.MEMBER) .executeChecked(); } public static Member findMemberByEmailAddress(String emailAddress) throws MemberNotFoundException { Member member = memberResource.list() .queryParam("email", emailAddress) .executeWithResult().getFirstIfExists(); if (member == null) { throw new MemberNotFoundException("Member with email " + emailAddress + " does not exist"); } return member; } public static Member findMemberByScreenName(String screenName) throws MemberNotFoundException { Member member = memberResource.list() .queryParam("screenName", screenName) .executeWithResult().getFirstIfExists(); if (member == null) { throw new MemberNotFoundException("Member with screenName " + screenName + " does not exist"); } return member; } public static List<Member> findMembersByIp(String ip) { return memberResource.service("findByIp", Member.TYPES.getTypeReference()) .queryParam("ip", ip).getList(); } public static List<Member> findByScreenNameOrName(String ip) { return memberResource.service("findByScreenNameOrName", Member.TYPES.getTypeReference()) .queryParam("name", ip).getList(); } public static Member findMemberByScreenNameNoRole(String screenName) throws MemberNotFoundException { try { return memberResource.service("findByScreenName", Member.class) .queryParam("screenName", screenName).getChecked(); } catch (EntityNotFoundException ignored) { throw new MemberNotFoundException("Member with screenName " + screenName + " does not exist"); } } public static Member findMemberByFacebookId(long facebookId) throws MemberNotFoundException { Member member = memberResource.list() .queryParam("facebookId", facebookId) .executeWithResult().getFirstIfExists(); if (member == null) { throw new MemberNotFoundException("Member with facebookId " + facebookId + " does not exist"); } return member; } public static Member findMemberByGoogleId(String googleId) throws MemberNotFoundException { Member member = memberResource.list() .queryParam("googleId", googleId) .executeWithResult().getFirstIfExists(); if (member == null) { throw new MemberNotFoundException("Member with googleId " + googleId + " does not exist"); } return member; } public static boolean updateMember(Member member) { return memberResource.update(member, member.getId_()) .cacheName(CacheName.MEMBER) .execute(); } public static boolean deleteMember(long memberId) { return memberResource.delete(memberId) .cacheName(CacheName.MEMBER) .execute(); } public static boolean isScreenNameUsed(String screenName) { return memberResource.service("isUsed", Boolean.class) .queryParam("screenName", screenName) .get(); } public static boolean isEmailUsed(String email) { return memberResource.service("isUsed", Boolean.class) .queryParam("email", email) .get(); } public static Long updateUserPassword(String forgotPasswordToken, String password) { password = encode(password); final Long result = memberResource.service("updateForgottenPassword", Long.class) .queryParam("forgotPasswordToken", forgotPasswordToken) .queryParam("password", password) .post(); //TODO: improve API so we can do more fine-grained cache refreshing ServiceRequestUtils.clearCache(CacheName.MEMBER); return result; } private static String encode(String password) { try { password = URLEncoder.encode(password, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new InternalException(e); } return password; } public static boolean isForgotPasswordTokenValid(String passwordToken) { try { return memberResource.service("validateForgotPasswordToken", Boolean.class) .queryParam("passwordToken", passwordToken) .getChecked(); } catch (EntityNotFoundException e) { return false; } } public static String createForgotPasswordToken(long memberId) { return memberResource.service("createForgotPasswordToken", String.class) .queryParam("memberId", memberId) //TODO: this should be posted! .get(); } public static LoginToken createLoginToken(long memberId) { return memberResource.service(memberId,"loginToken", LoginToken.class) .post(); } public static TokenValidity validateLoginToken(String tokenId, String tokenKey) { try { return loginTokenResource.service(tokenId, "validate", TokenValidity.class) .queryParam("tokenKey", tokenKey).get(); } catch (UncheckedEntityNotFoundException e) { return TokenValidity.INVALID; } } public static void invalidateLoginToken(String tokenId) { loginTokenResource.service(tokenId,"invalidate", Void.class) .post(); } public static Member getMemberForLoginToken(String tokenId) { return loginTokenResource.service(tokenId, "member", Member.class) .get(); } public static String generateScreenName(String lastName, String firstName) { return memberResource.service("generateScreenName", String.class) .queryParam("values", firstName, lastName) .get(); } public static String hashPassword(String password) { password = encode(password); return memberResource.service("hashPassword", String.class) .queryParam("password", password) .get(); } public static boolean validatePassword(String password, long memberId) { password = encode(password); return memberResource.service("validatePassword", Boolean.class) .queryParam("password", password) .queryParam("memberId", memberId) .post(); } public static boolean validatePassword(String password, String hashedPassword) { hashedPassword = encode(hashedPassword); password = encode(password); return memberResource.service("validatePassword", Boolean.class) .queryParam("password", password) .queryParam("hash", hashedPassword) .post(); } public static boolean updatePassword(long memberId, String newPassword) { newPassword = encode(newPassword); final Boolean result = memberResource.service(memberId, "updatePassword", Boolean.class) .queryParam("newPassword", newPassword) .post(); //TODO: improve endpoint for caching memberResource.get(memberId).withCache(CacheName.MEMBER).deleteFromCache(); return result; } public static Member register(Member member) { return memberResource.create(member).execute(); } public static boolean login(long memberId, String password, String remoteIp, String redirectUrl) throws PasswordLoginException, LockoutLoginException { LoginBean loginBean = new LoginBean(); loginBean.setPassword(password); loginBean.setIpAddress(remoteIp); loginBean.setRedirectUrl(redirectUrl); try { return memberResource.service(memberId, "login", Boolean.class) .post(loginBean); } catch (UncheckedEntityNotFoundException e) { throw new UncheckedMemberNotFoundException(memberId); } catch (Http401UnauthorizedException e) { throw new PasswordLoginException(); } catch (Http403ForbiddenException e) { throw new LockoutLoginException(); } } //TODO: this shouldn't be done manually public static LoginLog createLoginLog(long memberId, String ipAddress, String redirectUrl) { LoginLog loginLog = new LoginLog(); loginLog.setUserId(memberId); loginLog.setIpAddress(ipAddress); loginLog.setEntryUrl(redirectUrl != null ? redirectUrl.substring(0, Math.min(250, redirectUrl.length())) : ""); return loginLogResource.create(loginLog) .execute(); } public static boolean subscribeToNewsletter(long memberId) { return memberResource.service(memberId, "subscribe", Boolean.class).put(); } public static boolean unsubscribeFromNewsletter(long memberId) { return memberResource.service(memberId, "unsubscribe", Boolean.class).put(); } public static boolean isSubscribedToNewsletter(long memberId) { return memberResource.service(memberId, "isSubscribed", Boolean.class).get(); } public static boolean isUserInGroup(Long memberId, Long groupId) { return memberResource.service(memberId, "isMemberInGroup", Boolean.class) .queryParam("groupId", groupId) .get(); } public static void createUserGroupRole(Long memberId, Long groupId) { memberResource.service(memberId, "addMemberToGroup", Boolean.class) .queryParam("groupId", groupId) .get(); } }