package com.constellio.model.services.users;
import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.from;
import static com.constellio.model.services.users.XmlUserCredentialsManager.USER_CREDENTIALS_CONFIG;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.constellio.data.dao.dto.records.OptimisticLockingResolution;
import com.constellio.data.dao.managers.config.ConfigManager;
import com.constellio.data.dao.services.factories.DataLayerFactory;
import com.constellio.data.utils.BatchBuilderIterator;
import com.constellio.model.entities.records.Record;
import com.constellio.model.entities.records.Transaction;
import com.constellio.model.entities.records.wrappers.Collection;
import com.constellio.model.entities.records.wrappers.User;
import com.constellio.model.entities.security.global.GlobalGroup;
import com.constellio.model.entities.security.global.SolrGlobalGroup;
import com.constellio.model.entities.security.global.SolrUserCredential;
import com.constellio.model.entities.security.global.UserCredential;
import com.constellio.model.services.collections.CollectionsListManager;
import com.constellio.model.services.factories.ModelLayerFactory;
import com.constellio.model.services.records.RecordServices;
import com.constellio.model.services.records.RecordServicesException;
import com.constellio.model.services.records.SchemasRecordsServices;
import com.constellio.model.services.schemas.MetadataSchemaTypesAlteration;
import com.constellio.model.services.schemas.MetadataSchemasManager;
import com.constellio.model.services.schemas.builders.MetadataSchemaTypesBuilder;
import com.constellio.model.services.schemas.validators.EmailValidator;
import com.constellio.model.services.search.SearchServices;
import com.constellio.model.services.search.SearchServicesRuntimeException;
import com.constellio.model.services.search.query.logical.LogicalSearchQuery;
import com.constellio.model.services.users.UserServicesRuntimeException.UserServicesRuntimeException_NoSuchUser;
public class UserCredentialAndGlobalGroupsMigration {
private static final Logger LOGGER = LoggerFactory.getLogger(UserCredentialAndGlobalGroupsMigration.class);
private final ModelLayerFactory modelLayerFactory;
XmlUserCredentialsManager oldUserManager;
XmlGlobalGroupsManager oldGroupManager;
RecordServices recordServices;
SearchServices searchServices;
SchemasRecordsServices schemasRecordsServices;
MetadataSchemasManager schemasManager;
ConfigManager configManager;
public UserCredentialAndGlobalGroupsMigration(ModelLayerFactory modelLayerFactory) {
DataLayerFactory dataLayerFactory = modelLayerFactory.getDataLayerFactory();
this.configManager = dataLayerFactory.getConfigManager();
this.schemasManager = modelLayerFactory.getMetadataSchemasManager();
if (configManager.exist(XmlGlobalGroupsManager.CONFIG_FILE)) {
this.oldGroupManager = new XmlGlobalGroupsManager(configManager);
this.oldGroupManager.initialize();
}
if (configManager.exist(USER_CREDENTIALS_CONFIG)) {
this.oldUserManager = new XmlUserCredentialsManager(dataLayerFactory, modelLayerFactory,
modelLayerFactory.getConfiguration());
this.oldUserManager.initialize();
}
this.modelLayerFactory = modelLayerFactory;
this.recordServices = modelLayerFactory.newRecordServices();
this.searchServices = modelLayerFactory.newSearchServices();
this.schemasRecordsServices = new SchemasRecordsServices(Collection.SYSTEM_COLLECTION, modelLayerFactory);
if (schemasRecordsServices.credentialSchema().get(SolrUserCredential.EMAIL).isDefaultRequirement()) {
schemasManager.modify(Collection.SYSTEM_COLLECTION, new MetadataSchemaTypesAlteration() {
@Override
public void alter(MetadataSchemaTypesBuilder types) {
types.getSchema(SolrUserCredential.DEFAULT_SCHEMA).get(SolrUserCredential.EMAIL)
.setDefaultRequirement(false).setUniqueValue(false);
}
});
schemasRecordsServices = new SchemasRecordsServices(Collection.SYSTEM_COLLECTION, modelLayerFactory);
}
for (String collection : modelLayerFactory.getCollectionsListManager().getCollectionsExcludingSystem()) {
SchemasRecordsServices collectionSchemas = new SchemasRecordsServices(collection, modelLayerFactory);
if (collectionSchemas.userEmail().isDefaultRequirement()) {
schemasManager.modify(Collection.SYSTEM_COLLECTION, new MetadataSchemaTypesAlteration() {
@Override
public void alter(MetadataSchemaTypesBuilder types) {
types.getSchema(User.DEFAULT_SCHEMA).get(User.EMAIL)
.setDefaultRequirement(false).setUniqueValue(false);
}
});
schemasRecordsServices = new SchemasRecordsServices(Collection.SYSTEM_COLLECTION, modelLayerFactory);
}
}
}
public boolean isMigrationRequired() {
return oldGroupManager != null || oldUserManager != null;
}
private List<String> getExistingGroups() {
List<String> existingGroups = new ArrayList<>();
LogicalSearchQuery query = new LogicalSearchQuery(from(schemasRecordsServices.globalGroupSchemaType()).returnAll());
Iterator<Record> groupsIterator = searchServices.recordsIterator(query, 1000);
while (groupsIterator.hasNext()) {
existingGroups.add((String) groupsIterator.next().get(schemasRecordsServices.globalGroupCode()));
}
return existingGroups;
}
private Map<String, SolrUserCredential> getExistingUsers(List<String> validUsernames, List<String> invalidUsernames) {
Map<String, SolrUserCredential> existingUsers = new HashMap<>();
LogicalSearchQuery query = new LogicalSearchQuery(from(schemasRecordsServices.credentialSchemaType()).returnAll());
Iterator<Record> usersIterator = searchServices.recordsIterator(query, 1000);
while (usersIterator.hasNext()) {
SolrUserCredential userCredential = (SolrUserCredential) schemasRecordsServices
.wrapUserCredential(usersIterator.next());
String cleanedUsername = UserUtils.cleanUsername(userCredential.getUsername());
if (!cleanedUsername.equals(userCredential.getUsername())) {
invalidUsernames.add(userCredential.getUsername());
} else {
validUsernames.add(userCredential.getUsername());
}
existingUsers.put(cleanedUsername, userCredential);
userCredential.setUsername(UserUtils.cleanUsername(userCredential.getUsername()));
}
return existingUsers;
}
public void migrateUserAndGroups() {
Transaction transaction = new Transaction();
List<String> existingGroups = getExistingGroups();
List<String> invalidUsernames = new ArrayList<>();
List<String> validUsernames = new ArrayList<>();
Map<String, SolrUserCredential> existingUsers = getExistingUsers(validUsernames, invalidUsernames);
if (oldGroupManager != null) {
for (GlobalGroup oldGroup : oldGroupManager.getAllGroups()) {
if (!existingGroups.contains(oldGroup.getCode())) {
SolrGlobalGroup newGroup = (SolrGlobalGroup) schemasRecordsServices.newGlobalGroup();
newGroup.setCode(oldGroup.getCode());
newGroup.setName(oldGroup.getName());
newGroup.setTitle(oldGroup.getName());
newGroup.setStatus(oldGroup.getStatus());
newGroup.setUsersAutomaticallyAddedToCollections(oldGroup.getUsersAutomaticallyAddedToCollections());
if (oldGroup.getParent() != null) {
newGroup.setParent(oldGroup.getParent());
}
if (isValid(newGroup)) {
transaction.add(newGroup);
}
}
}
}
try {
transaction.setOptimisticLockingResolution(OptimisticLockingResolution.EXCEPTION);
recordServices.execute(transaction);
} catch (RecordServicesException e) {
throw new RuntimeException(e);
}
Iterator<List<UserCredential>> userCredentialBatchesIterator = new BatchBuilderIterator<>(
oldUserManager.getUserCredentials().iterator(), 100);
while (userCredentialBatchesIterator.hasNext()) {
transaction = new Transaction();
for (UserCredential userCredential : userCredentialBatchesIterator.next()) {
SolrUserCredential solrUserCredential = toSolrUserCredential(userCredential, existingUsers);
if (isValid(solrUserCredential)) {
transaction.add(solrUserCredential);
if (!solrUserCredential.getUsername().equals(userCredential.getUsername())) {
invalidUsernames.add(userCredential.getUsername());
}
}
}
try {
transaction.setOptimisticLockingResolution(OptimisticLockingResolution.EXCEPTION);
recordServices.execute(transaction);
} catch (RecordServicesException e) {
throw new RuntimeException(e);
}
}
correctUsernameInAllCollections(validUsernames, invalidUsernames);
Iterator<Record> userCredentialIterator = searchServices.recordsIterator(new LogicalSearchQuery(
from(schemasRecordsServices.credentialSchemaType()).returnAll()), 100);
long nbUsers = searchServices.getResultsCount(from(schemasRecordsServices.credentialSchemaType()).returnAll());
int done = 0;
UserServices userServices = modelLayerFactory.newUserServices();
while (userCredentialIterator.hasNext()) {
UserCredential userCredential = schemasRecordsServices.wrapCredential(userCredentialIterator.next());
if (validUsernames.contains(userCredential.getUsername())) {
for (String collection : userCredential.getCollections()) {
try {
if (userServices.getUserInCollection(userCredential.getUsername(), collection) == null) {
userServices.sync(userCredential);
}
} catch (SearchServicesRuntimeException.TooManyRecordsInSingleSearchResult e) {
SchemasRecordsServices collectionSchemas = new SchemasRecordsServices(collection, modelLayerFactory);
List<Record> userRecords = searchServices.search(new LogicalSearchQuery(
from(collectionSchemas.userSchemaType())
.where(collectionSchemas.userUsername()).isEqualTo(userCredential.getUsername())));
List<User> users = collectionSchemas.wrapUsers(userRecords);
Transaction transaction2 = new Transaction();
for (int i = 1; i < users.size(); i++) {
transaction2.add(users.get(i)).setUsername(userCredential.getUsername() + "-disabled");
}
try {
transaction2.getRecordUpdateOptions().setValidationsEnabled(false);
transaction.setOptimisticLockingResolution(OptimisticLockingResolution.EXCEPTION);
recordServices.execute(transaction2);
} catch (RecordServicesException e1) {
throw new RuntimeException(e1);
}
userServices.sync(userCredential);
}
}
}
LOGGER.info("Validating users after migration : " + ++done + "/" + nbUsers);
}
oldUserManager.close();
if (oldGroupManager != null) {
oldGroupManager.close();
}
configManager.move(USER_CREDENTIALS_CONFIG, USER_CREDENTIALS_CONFIG + ".old");
if (oldGroupManager != null) {
configManager.move(XmlGlobalGroupsManager.CONFIG_FILE, XmlGlobalGroupsManager.CONFIG_FILE + ".old");
}
}
private boolean isValid(SolrGlobalGroup group) {
return StringUtils.isNotBlank(group.getCode());
}
private boolean isValid(UserCredential user) {
return StringUtils.isNotBlank(user.getUsername());
}
private void correctUsernameInAllCollections(List<String> validUsernames, List<String> invalidUsernames) {
UserServices userServices = modelLayerFactory.newUserServices();
CollectionsListManager collectionManager = modelLayerFactory.getCollectionsListManager();
for (String collection : collectionManager.getCollections()) {
Transaction transaction = new Transaction();
for (String username : invalidUsernames) {
try {
User user = userServices.getUserInCollectionCaseSensitive(username, collection);
if (user != null) {
String cleanedUsername = UserUtils.cleanUsername(user.getUsername());
if (!validUsernames.contains(cleanedUsername)) {
user.setUsername(cleanedUsername);
transaction.add(user);
validUsernames.add(cleanedUsername);
}
}
} catch (UserServicesRuntimeException_NoSuchUser e) {
//OK
}
}
transaction.setOptimisticLockingResolution(OptimisticLockingResolution.EXCEPTION);
transaction.setOptions(transaction.getRecordUpdateOptions().setValidationsEnabled(false));
try {
recordServices.execute(transaction);
} catch (RecordServicesException e) {
throw new RuntimeException(e);
}
}
}
private SolrUserCredential toSolrUserCredential(UserCredential userCredential,
Map<String, SolrUserCredential> existingUsers) {
String correctedUsername = UserUtils.cleanUsername(userCredential.getUsername());
SolrUserCredential newUserCredential = existingUsers.get(correctedUsername);
if (newUserCredential == null) {
newUserCredential = (SolrUserCredential) schemasRecordsServices.newCredential();
}
newUserCredential.setFirstName(userCredential.getFirstName());
newUserCredential.setLastName(userCredential.getLastName());
newUserCredential.setUsername(correctedUsername);
newUserCredential.setDn(userCredential.getDn());
newUserCredential.setDomain(userCredential.getDomain());
if (EmailValidator.isValid(userCredential.getEmail())) {
newUserCredential.setEmail(userCredential.getEmail());
}
newUserCredential.setAccessTokens(userCredential.getAccessTokens());
newUserCredential.setCollections(userCredential.getCollections());
newUserCredential.setMsExchDelegateListBL(userCredential.getMsExchDelegateListBL());
newUserCredential.setStatus(userCredential.getStatus());
newUserCredential.setServiceKey(userCredential.getServiceKey());
newUserCredential.setSystemAdmin(userCredential.isSystemAdmin());
newUserCredential.setGlobalGroups(userCredential.getGlobalGroups());
return newUserCredential;
}
}