diff --git a/src/main/java/biz/nynja/account/StartupScriptsListener.java b/src/main/java/biz/nynja/account/StartupScriptsListener.java index 259e47ae1e4654e1495b0fae5d47dfe7dadbc70f..4dcf578dae24fd8e7e8a238d8038603141dfc154 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); } -} \ No newline at end of file + + 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); + } +} diff --git a/src/main/java/biz/nynja/account/codecs/AuthenticationProviderCodec.java b/src/main/java/biz/nynja/account/codecs/AuthenticationProviderCodec.java index c1260f786a8d5285ab5adffa3df7e95c6ec8f171..801eba03279f5d8844f1993be9c4ca49885e30a0 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 cbfd96b357421e9323f1c6ef06ee7f7d6de85c38..bf4f277d8b252f93163e93ee23bc9308e7bfc0bd 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 20cfeb5519248f2abd02effe822f2b949962fae6..8254fa4db187bf96f03c6083be4728b54340cfe2 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 2a9c034eb3043f5c9e42cd9f0e758519496544ba..e53acce2cd4421d59c760b64cde584919cd4cfa0 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 3ab43dc5919064db1854bded76132351fd6457fb..ca217e660c9994600e76e4621e0376f30c19e90b 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(); + }