From 0e5d8eec12946d893658150b5b400250c6e216a7 Mon Sep 17 00:00:00 2001 From: Stanimir Penkov Date: Tue, 18 Dec 2018 16:44:00 +0200 Subject: [PATCH 1/2] NY-6145: Add implementation for updating current DB model to support searchable option Signed-off-by: Stanimir Penkov --- .../nynja/account/StartupScriptsListener.java | 43 +++++++++ .../codecs/AuthenticationProviderCodec.java | 4 +- .../models/AuthenticationProvider.java | 68 ++++++++++++- .../ProfileByAuthenticationProvider.java | 28 +++++- .../AccountRepositoryAdditional.java | 2 + .../AccountRepositoryAdditionalImpl.java | 95 +++++++++++++++++++ ...ileByAuthenticationProviderRepository.java | 6 ++ 7 files changed, 243 insertions(+), 3 deletions(-) diff --git a/src/main/java/biz/nynja/account/StartupScriptsListener.java b/src/main/java/biz/nynja/account/StartupScriptsListener.java index 259e47a..27c811f 100644 --- a/src/main/java/biz/nynja/account/StartupScriptsListener.java +++ b/src/main/java/biz/nynja/account/StartupScriptsListener.java @@ -1,16 +1,23 @@ +/** + * Copyright (C) 2018 Nynja Inc. All rights reserved. + */ package biz.nynja.account; import java.util.Arrays; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import com.datastax.driver.core.Session; +import com.datastax.driver.core.exceptions.InvalidQueryException; import biz.nynja.account.configuration.CassandraConfig; +import biz.nynja.account.repositories.AccountRepositoryAdditional; /** * This acts as {@link CassandraConfig} startupScripts executor but activated after the spring has setup the needed @@ -22,18 +29,45 @@ import biz.nynja.account.configuration.CassandraConfig; @Component public class StartupScriptsListener { + private static final Logger logger = LoggerFactory.getLogger(StartupScriptsListener.class); private String keyspace; @Autowired private Session session; + @Autowired + private AccountRepositoryAdditional accountRepositoryAdditional; + @EventListener(ContextRefreshedEvent.class) public void contextRefreshedEvent() { keyspace = session.getLoggedKeyspace(); + boolean searchableColumnAlreadyExists = false, searchableFeildAlreadyExists = false; for (String script : getStartupScripts()) { session.execute(script); } + + try { + // add searchable column + session.execute(getScriptsForSearchableOption().get(0)); + } catch (InvalidQueryException e) { + logger.warn("Exception while executing script for adding searchable column: {}", e.getMessage()); + // In the current case InvalidQueryException is used to confirm that the searchable column already exists. + searchableColumnAlreadyExists = true; + } + + try { + // add searchable field + session.execute(getScriptsForSearchableOption().get(1)); + } catch (InvalidQueryException e) { + logger.warn("Exception while executing script for adding searchable field: {}", e.getMessage()); + // In the current case InvalidQueryException is used to confirm that the searchable column already exists. + searchableFeildAlreadyExists = true; + } + + if (searchableColumnAlreadyExists && searchableFeildAlreadyExists) { + accountRepositoryAdditional.removeNullsForSearchableOption(); + } } private List getStartupScripts() { @@ -87,4 +121,13 @@ public class StartupScriptsListener { scriptAccountViewByFirstName, scriptAccountViewByLastName, scriptAccountViewByAccessStatus, scriptAccountViewByCreationTimestamp, scriptAccountViewByLastUpdateTimestamp); } + + private List getScriptsForSearchableOption() { + String addSearchableColumnScript = "ALTER TABLE " + keyspace + + ".profilebyauthenticationprovider ADD searchable boolean;"; + + String addSearchableToAuthenticationProviderType = "ALTER TYPE authenticationprovider add searchable boolean;"; + + return Arrays.asList(addSearchableColumnScript, addSearchableToAuthenticationProviderType); + } } \ No newline at end of file diff --git a/src/main/java/biz/nynja/account/codecs/AuthenticationProviderCodec.java b/src/main/java/biz/nynja/account/codecs/AuthenticationProviderCodec.java index c1260f7..801eba0 100644 --- a/src/main/java/biz/nynja/account/codecs/AuthenticationProviderCodec.java +++ b/src/main/java/biz/nynja/account/codecs/AuthenticationProviderCodec.java @@ -56,12 +56,14 @@ public class AuthenticationProviderCodec extends TypeCodec foundExistingAuthenticationProviderByTypeAndValue(String typeToFind, + String valueToFind, Set authProvidersSet) { + for (AuthenticationProvider ap : authProvidersSet) { + if (ap.getType().equals(typeToFind) && ap.getValue().equals(valueToFind)) { + return Optional.of(ap); + } + } + return Optional.empty(); + } } diff --git a/src/main/java/biz/nynja/account/models/ProfileByAuthenticationProvider.java b/src/main/java/biz/nynja/account/models/ProfileByAuthenticationProvider.java index cbfd96b..bf4f277 100644 --- a/src/main/java/biz/nynja/account/models/ProfileByAuthenticationProvider.java +++ b/src/main/java/biz/nynja/account/models/ProfileByAuthenticationProvider.java @@ -5,6 +5,8 @@ package biz.nynja.account.models; import java.util.UUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.data.cassandra.core.cql.PrimaryKeyType; import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn; import org.springframework.data.cassandra.core.mapping.Table; @@ -12,10 +14,12 @@ import org.springframework.data.cassandra.core.mapping.Table; @Table public class ProfileByAuthenticationProvider { + private static final Logger logger = LoggerFactory.getLogger(ProfileByAuthenticationProvider.class); @PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED) private String authenticationProvider; @PrimaryKeyColumn(ordinal = 1, type = PrimaryKeyType.PARTITIONED) private String authenticationProviderType; + private Boolean searchable; private UUID profileId; public String getAuthenticationProvider() { @@ -42,6 +46,21 @@ public class ProfileByAuthenticationProvider { this.profileId = profileId; } + public Boolean getSearchable() { + return searchable; + } + + public void setSearchable(Boolean searchable) { + // if searchable is null, set the default value: true + if (searchable == null) { + logger.warn("The searchable option can not be null for {}:{}. Default option will be used instead.", + getAuthenticationProviderType(), getAuthenticationProvider()); + this.searchable = Boolean.TRUE; + } else { + this.searchable = searchable; + } + } + @Override public int hashCode() { final int prime = 31; @@ -49,6 +68,7 @@ public class ProfileByAuthenticationProvider { result = prime * result + ((authenticationProvider == null) ? 0 : authenticationProvider.hashCode()); result = prime * result + ((authenticationProviderType == null) ? 0 : authenticationProviderType.hashCode()); result = prime * result + ((profileId == null) ? 0 : profileId.hashCode()); + result = prime * result + ((searchable == null) ? 0 : searchable.hashCode()); return result; } @@ -76,6 +96,11 @@ public class ProfileByAuthenticationProvider { return false; } else if (!profileId.equals(other.profileId)) return false; + if (searchable == null) { + if (other.searchable != null) + return false; + } else if (!searchable.equals(other.searchable)) + return false; return true; } @@ -83,7 +108,8 @@ public class ProfileByAuthenticationProvider { public String toString() { return new StringBuilder("ProfileByAuthenticationProvider [authenticationProvider=") .append(authenticationProvider).append(", authenticationProviderType=") - .append(authenticationProviderType).append(", profileId=").append(profileId).append("]").toString(); + .append(authenticationProviderType).append(", profileId=").append(profileId).append(", searchable=") + .append(searchable).append("]").toString(); } } diff --git a/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditional.java b/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditional.java index 20cfeb5..8254fa4 100644 --- a/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditional.java +++ b/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditional.java @@ -54,4 +54,6 @@ public interface AccountRepositoryAdditional { PendingAccount savePendingAccount(PendingAccount updatedPendingAccount); Optional getAccountByLoginOption(AuthenticationProvider loginOption) throws IncorrectAccountCountException; + + void removeNullsForSearchableOption(); } diff --git a/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditionalImpl.java b/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditionalImpl.java index 2a9c034..e53acce 100644 --- a/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditionalImpl.java +++ b/src/main/java/biz/nynja/account/repositories/AccountRepositoryAdditionalImpl.java @@ -811,4 +811,99 @@ public class AccountRepositoryAdditionalImpl implements AccountRepositoryAdditio pendingAccount.getProfileId(), pendingAccount.getAuthenticationProvider(), pendingAccount.getAuthenticationProviderType(), pendingAccount.getCreationTimestamp()); } + + public void removeNullsForSearchableOption() { + List profilesByAuthenticationProvider = profileByAuthenticationProviderRepository + .findAll(); + for (int i = 0; i < profilesByAuthenticationProvider.size(); i++) { + if (profilesByAuthenticationProvider.get(i).getSearchable() == null) { + logger.error("Found null for searchable option in profile: {}", + profilesByAuthenticationProvider.get(i).getProfileId()); + Profile profileToUpdate = profileRepository + .findByProfileId(profilesByAuthenticationProvider.get(i).getProfileId()); + if (profileToUpdate == null) { + logger.error( + "Error replacing null with default searchable option for auth provider {}:{} in profile {}. Profile not found.", + profilesByAuthenticationProvider.get(i).getAuthenticationProviderType(), + profilesByAuthenticationProvider.get(i).getAuthenticationProvider(), + profilesByAuthenticationProvider.get(i).getProfileId()); + continue; + } + + logger.info("Replacing null with default searchable option for profile {}", + profilesByAuthenticationProvider.get(i).getProfileId()); + + CassandraBatchOperations batchOperations = cassandraTemplate.batchOps(); + WriteResult wr; + try { + setDefaultSearchableInProfileByAuthenticationProvider(batchOperations, + profilesByAuthenticationProvider.get(i)); + if (!setDefaultSearchableInProfile(batchOperations, profileToUpdate, + profilesByAuthenticationProvider.get(i).getAuthenticationProviderType(), + profilesByAuthenticationProvider.get(i).getAuthenticationProvider())) { + logger.error( + "Error replacing null with default searchable option for profile {}: auth provider {}:{}.", + profilesByAuthenticationProvider.get(i).getProfileId(), + profilesByAuthenticationProvider.get(i).getAuthenticationProviderType(), + profilesByAuthenticationProvider.get(i).getAuthenticationProvider()); + } + wr = batchOperations.execute(); + } catch (IllegalArgumentException | IllegalStateException e) { + logger.debug( + "Exception while replacing null with default searchable option for auth provider {}:{} in profile{}: {}.", + profilesByAuthenticationProvider.get(i).getAuthenticationProviderType(), + profilesByAuthenticationProvider.get(i).getAuthenticationProvider(), + profilesByAuthenticationProvider.get(i).getProfileId(), e.getMessage()); + continue; + } + if (wr != null && wr.wasApplied()) { + logger.info("Successfully replaced null with default searchable option in profile {}.", + profilesByAuthenticationProvider.get(i).getProfileId()); + } + } + } + } + + private void setDefaultSearchableInProfileByAuthenticationProvider(CassandraBatchOperations batchOps, + ProfileByAuthenticationProvider profileByAuthenticationProvider) { + profileByAuthenticationProvider.setSearchable(Boolean.TRUE); + batchOps.update(profileByAuthenticationProvider); + } + + private boolean setDefaultSearchableInProfile(CassandraBatchOperations batchOps, Profile profileToUpdate, + String authProviderType, String authProvider) { + Set authProviderSet = profileToUpdate.getAuthenticationProviders(); + if (authProviderSet == null) { + logger.error("No existing auth providers found for profile {}.", profileToUpdate.getProfileId()); + return false; + } + + AuthenticationProvider authProviderToUpdate = AuthenticationProvider + .createAuthenticationProviderWithSearchableOption(authProviderType, authProvider, Boolean.TRUE); + Optional currentAuthProvider = AuthenticationProvider + .foundExistingAuthenticationProviderByTypeAndValue(authProviderType, authProvider, authProviderSet); + if (!currentAuthProvider.isPresent()) { + logger.error("Existing auth provider {}:{} not found for profile {}.", authProviderType, authProvider, + profileToUpdate.getProfileId()); + return false; + } + + if (!authProviderSet.remove(currentAuthProvider.get())) { + logger.error("Error setting default searchable option for auth provider {}:{} in profile {}.", + currentAuthProvider.get().getType(), currentAuthProvider.get().getValue(), + profileToUpdate.getProfileId()); + return false; + } + + if (!authProviderSet.add(authProviderToUpdate)) { + logger.error("Error setting default searchable option for auth provider {}:{} in profile {}.", + currentAuthProvider.get().getType(), currentAuthProvider.get().getValue(), + profileToUpdate.getProfileId()); + return false; + } + + profileToUpdate.setAuthenticationProviders(authProviderSet); + batchOps.update(profileToUpdate); + return true; + } } diff --git a/src/main/java/biz/nynja/account/repositories/ProfileByAuthenticationProviderRepository.java b/src/main/java/biz/nynja/account/repositories/ProfileByAuthenticationProviderRepository.java index 3ab43dc..ca217e6 100644 --- a/src/main/java/biz/nynja/account/repositories/ProfileByAuthenticationProviderRepository.java +++ b/src/main/java/biz/nynja/account/repositories/ProfileByAuthenticationProviderRepository.java @@ -3,6 +3,8 @@ */ package biz.nynja.account.repositories; +import java.util.List; + import org.springframework.data.cassandra.repository.CassandraRepository; import org.springframework.stereotype.Repository; @@ -14,4 +16,8 @@ public interface ProfileByAuthenticationProviderRepository ProfileByAuthenticationProvider findByAuthenticationProviderAndAuthenticationProviderType(String authProvider, String authProviderType); + + // The method is temporary used to update the existing null values for searchable column in update scripts. + List findAll(); + } -- GitLab From 5e574bedd07b385d876ecc384e2a3e233405e150 Mon Sep 17 00:00:00 2001 From: Stanimir Penkov Date: Tue, 18 Dec 2018 16:46:24 +0200 Subject: [PATCH 2/2] NY-6145: Add new line Signed-off-by: Stanimir Penkov --- src/main/java/biz/nynja/account/StartupScriptsListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/biz/nynja/account/StartupScriptsListener.java b/src/main/java/biz/nynja/account/StartupScriptsListener.java index 27c811f..4dcf578 100644 --- a/src/main/java/biz/nynja/account/StartupScriptsListener.java +++ b/src/main/java/biz/nynja/account/StartupScriptsListener.java @@ -130,4 +130,4 @@ public class StartupScriptsListener { return Arrays.asList(addSearchableColumnScript, addSearchableToAuthenticationProviderType); } -} \ No newline at end of file +} -- GitLab