package com.constellio.app.services.collections;
import static com.constellio.data.conf.DigitSeparatorMode.THREE_LEVELS_OF_ONE_DIGITS;
import static com.constellio.data.conf.DigitSeparatorMode.TWO_DIGITS;
import static com.constellio.data.conf.HashingEncoding.BASE64;
import static com.constellio.data.conf.HashingEncoding.BASE64_URL_ENCODED;
import static java.util.Arrays.asList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.joda.time.LocalDateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.constellio.app.services.collections.CollectionsManagerRuntimeException.CollectionsManagerRuntimeException_CannotCreateCollectionRecord;
import com.constellio.app.services.collections.CollectionsManagerRuntimeException.CollectionsManagerRuntimeException_CannotMigrateCollection;
import com.constellio.app.services.collections.CollectionsManagerRuntimeException.CollectionsManagerRuntimeException_CannotRemoveCollection;
import com.constellio.app.services.collections.CollectionsManagerRuntimeException.CollectionsManagerRuntimeException_CollectionLanguageMustIncludeSystemMainDataLanguage;
import com.constellio.app.services.collections.CollectionsManagerRuntimeException.CollectionsManagerRuntimeException_CollectionNotFound;
import com.constellio.app.services.collections.CollectionsManagerRuntimeException.CollectionsManagerRuntimeException_CollectionWithGivenCodeAlreadyExists;
import com.constellio.app.services.collections.CollectionsManagerRuntimeException.CollectionsManagerRuntimeException_InvalidCode;
import com.constellio.app.services.collections.CollectionsManagerRuntimeException.CollectionsManagerRuntimeException_InvalidLanguage;
import com.constellio.app.services.extensions.ConstellioModulesManagerImpl;
import com.constellio.app.services.migrations.MigrationServices;
import com.constellio.app.services.systemSetup.SystemGlobalConfigsManager;
import com.constellio.data.dao.dto.records.RecordsFlushing;
import com.constellio.data.dao.dto.records.TransactionDTO;
import com.constellio.data.dao.managers.StatefulService;
import com.constellio.data.dao.managers.config.ConfigManager;
import com.constellio.data.dao.managers.config.ConfigManagerException.OptimisticLockingConfiguration;
import com.constellio.data.dao.services.bigVault.RecordDaoException.OptimisticLocking;
import com.constellio.data.dao.services.factories.DataLayerFactory;
import com.constellio.data.utils.Delayed;
import com.constellio.model.entities.Language;
import com.constellio.model.entities.Taxonomy;
import com.constellio.model.entities.records.Record;
import com.constellio.model.entities.records.wrappers.Collection;
import com.constellio.model.entities.records.wrappers.User;
import com.constellio.model.entities.schemas.Metadata;
import com.constellio.model.entities.schemas.MetadataSchema;
import com.constellio.model.entities.schemas.MetadataSchemaTypes;
import com.constellio.model.entities.schemas.Schemas;
import com.constellio.model.entities.security.global.SolrUserCredential;
import com.constellio.model.entities.security.global.UserCredential;
import com.constellio.model.entities.security.global.UserCredentialStatus;
import com.constellio.model.services.collections.CollectionsListManager;
import com.constellio.model.services.factories.ModelLayerFactory;
import com.constellio.model.services.factories.SystemCollectionListener;
import com.constellio.model.services.records.RecordServices;
import com.constellio.model.services.records.RecordServicesException;
import com.constellio.model.services.records.RecordServicesRuntimeException;
import com.constellio.model.services.records.SchemasRecordsServices;
import com.constellio.model.services.records.cache.CacheConfig;
import com.constellio.model.services.records.cache.RecordsCache;
import com.constellio.model.services.security.authentification.AuthenticationService;
import com.constellio.model.services.taxonomies.TaxonomiesManager;
import com.constellio.model.services.users.UserCredentialAndGlobalGroupsMigration;
import com.constellio.model.services.users.UserServices;
import com.constellio.model.services.users.UserServicesRuntimeException.UserServicesRuntimeException_NoSuchUser;
public class CollectionsManager implements StatefulService {
private static final Logger LOGGER = LoggerFactory.getLogger(CollectionsManager.class);
private final Delayed<MigrationServices> migrationServicesDelayed;
private final CollectionsListManager collectionsListManager;
private final ConstellioModulesManagerImpl constellioModulesManager;
private final ModelLayerFactory modelLayerFactory;
private final DataLayerFactory dataLayerFactory;
private final SystemGlobalConfigsManager systemGlobalConfigsManager;
private Map<String, List<String>> collectionLanguagesCache = new HashMap<>();
private List<String> newDisabledCollections = new ArrayList<>();
public CollectionsManager(ModelLayerFactory modelLayerFactory, ConstellioModulesManagerImpl constellioModulesManager,
Delayed<MigrationServices> migrationServicesDelayed, SystemGlobalConfigsManager systemGlobalConfigsManager) {
this.modelLayerFactory = modelLayerFactory;
this.constellioModulesManager = constellioModulesManager;
this.collectionsListManager = modelLayerFactory.getCollectionsListManager();
this.dataLayerFactory = modelLayerFactory.getDataLayerFactory();
this.migrationServicesDelayed = migrationServicesDelayed;
this.systemGlobalConfigsManager = systemGlobalConfigsManager;
}
@Override
public void initialize() {
if (!collectionsListManager.getCollections().contains(Collection.SYSTEM_COLLECTION)) {
createSystemCollection();
}
if (!modelLayerFactory.getMetadataSchemasManager().getSchemaTypes(Collection.SYSTEM_COLLECTION)
.hasType(SolrUserCredential.SCHEMA_TYPE)) {
initializeSystemCollection();
}
disableCollectionsWithoutSchemas();
SchemasRecordsServices schemas = new SchemasRecordsServices(Collection.SYSTEM_COLLECTION, modelLayerFactory);
if (!schemas.getTypes().hasType(SolrUserCredential.SCHEMA_TYPE)) {
for (SystemCollectionListener listener : modelLayerFactory.getSystemCollectionListeners()) {
listener.systemCollectionCreated();
}
}
schemas = new SchemasRecordsServices(Collection.SYSTEM_COLLECTION, modelLayerFactory);
RecordsCache cache = modelLayerFactory.getRecordsCaches().getCache(Collection.SYSTEM_COLLECTION);
if (schemas.getTypes().hasType(SolrUserCredential.SCHEMA_TYPE)) {
cache.configureCache(CacheConfig.permanentCache(schemas.credentialSchemaType()));
cache.configureCache(CacheConfig.permanentCache(schemas.globalGroupSchemaType()));
}
}
private void disableCollectionsWithoutSchemas() {
for (String collection : collectionsListManager.getCollections()) {
try {
MetadataSchemaTypes types = modelLayerFactory.getMetadataSchemasManager().getSchemaTypes(collection);
types.getSchemaType(User.SCHEMA_TYPE);
} catch (Exception e) {
collectionsListManager.remove(collection);
newDisabledCollections.add(collection);
LOGGER.warn("Collection '" + collection + "' has been disabled since it have no schemas "
+ "(probably a problem during the creation of the collection)");
}
}
}
private void createSystemCollection() {
String mainDataLanguage = modelLayerFactory.getConfiguration().getMainDataLanguage();
List<String> languages = asList(mainDataLanguage);
createCollectionInCurrentVersion(Collection.SYSTEM_COLLECTION, languages);
}
private void initializeSystemCollection() {
for (SystemCollectionListener listener : modelLayerFactory.getSystemCollectionListeners()) {
listener.systemCollectionCreated();
}
}
public List<String> getCollectionCodes() {
return collectionsListManager.getCollections();
}
public List<String> getCollectionCodesExcludingSystem() {
return collectionsListManager.getCollectionsExcludingSystem();
}
void addGlobalGroupsInCollection(String code) {
modelLayerFactory.newUserServices().addGlobalGroupsInCollection(code);
}
public Record createCollectionRecordWithCode(String code, String name, List<String> languages) {
RecordServices recordServices = modelLayerFactory.newRecordServices();
Record record = recordServices.newRecordWithSchema(collectionSchema(code));
record.set(collectionCodeMetadata(code), code);
record.set(collectionNameMetadata(code), name);
record.set(Schemas.TITLE, name);
record.set(collectionLanguages(code), languages);
try {
recordServices.add(record);
return record;
} catch (RecordServicesException e) {
throw new CollectionsManagerRuntimeException_CannotCreateCollectionRecord(code, e);
}
}
Record createCollectionRecordWithCode(String code, List<String> languages) {
return createCollectionRecordWithCode(code, code, languages);
}
MetadataSchema collectionSchema(String collection) {
MetadataSchemaTypes types = modelLayerFactory.getMetadataSchemasManager().getSchemaTypes(collection);
return types.getSchema(Collection.SCHEMA_TYPE + "_default");
}
private Metadata collectionCodeMetadata(String collection) {
return collectionSchema(collection).getMetadata(Collection.CODE);
}
private Metadata collectionNameMetadata(String collection) {
return collectionSchema(collection).getMetadata(Collection.NAME);
}
private Metadata collectionLanguages(String collection) {
return collectionSchema(collection).getMetadata(Collection.LANGUAGES);
}
public void createCollectionConfigs(String code) {
modelLayerFactory.getMetadataSchemasManager().createCollectionSchemas(code);
modelLayerFactory.getTaxonomiesManager().createCollectionTaxonomies(code);
modelLayerFactory.getAuthorizationDetailsManager().createCollectionAuthorizationDetail(code);
modelLayerFactory.getRolesManager().createCollectionRole(code);
modelLayerFactory.getWorkflowsConfigManager().createCollectionWorkflows(code);
modelLayerFactory.getWorkflowExecutionIndexManager().createCollectionWorkflowsExecutionIndex(code);
modelLayerFactory.getSearchBoostManager().createCollectionSearchBoost(code);
}
public Collection getCollection(String code) {
try {
Record record = modelLayerFactory.newRecordServices().getDocumentById(code);
MetadataSchemaTypes types = modelLayerFactory.getMetadataSchemasManager().getSchemaTypes(code);
return new Collection(record, types);
} catch (RecordServicesRuntimeException.NoSuchRecordWithId e) {
throw new CollectionsManagerRuntimeException_CollectionNotFound(code, e);
}
}
public void deleteCollection(final String collection) {
ConfigManager configManager = dataLayerFactory.getConfigManager();
removeCollectionFromUserCredentials(collection);
removeCollectionFromGlobalGroups(collection);
removeFromCollectionsListManager(collection);
removeCollectionFromBigVault(collection);
removeCollectionFromVersionProperties(collection, configManager);
removeRemoveAllConfigsOfCollection(collection, configManager);
removeCollectionFromCache(collection);
}
private void removeCollectionFromCache(String collection) {
modelLayerFactory.getRecordsCaches().invalidate(collection);
}
private void removeRemoveAllConfigsOfCollection(final String collection, ConfigManager configManager) {
configManager.deleteAllConfigsIn("/" + collection);
}
private void removeCollectionFromBigVault(final String collection) {
TransactionDTO transactionDTO = newTransactionDTO();
ModifiableSolrParams params = newModifiableSolrParams();
params.set("q", "collection_s:" + collection);
transactionDTO = transactionDTO.withDeletedByQueries(params);
try {
dataLayerFactory.newRecordDao().execute(transactionDTO);
} catch (OptimisticLocking optimisticLocking) {
throw new CollectionsManagerRuntimeException_CannotRemoveCollection(collection, optimisticLocking);
}
}
private void removeCollectionFromVersionProperties(final String collection, ConfigManager configManager) {
constellioModulesManager.removeCollectionFromVersionProperties(collection, configManager);
}
private void removeCollectionFromUserCredentials(final String collection) {
modelLayerFactory.getUserCredentialsManager().removeCollection(collection);
}
private void removeCollectionFromGlobalGroups(final String collection) {
modelLayerFactory.getGlobalGroupsManager().removeCollection(collection);
}
private void removeFromCollectionsListManager(final String collection) {
collectionsListManager.remove(collection);
}
// private PropertiesAlteration newRemoveCollectionPropertiesAlteration(final String collection) {
// return new PropertiesAlteration() {
// @Override
// public void alter(Map<String, String> properties) {
// if (properties.containsKey(collection + "_version")) {
// properties.remove(collection + "_version");
// }
// }
// };
// }
TransactionDTO newTransactionDTO() {
return new TransactionDTO(RecordsFlushing.NOW);
}
ModifiableSolrParams newModifiableSolrParams() {
return new ModifiableSolrParams();
}
public List<String> getCollectionLanguages(final String collection) {
if (Collection.SYSTEM_COLLECTION.equals(collection)) {
return asList(modelLayerFactory.getConfiguration().getMainDataLanguage());
}
List<String> collectionLanguages = collectionLanguagesCache.get(collection);
if (collectionLanguages == null) {
try {
collectionLanguages = getCollection(collection).getLanguages();
collectionLanguagesCache.put(collection, collectionLanguages);
} catch (CollectionsManagerRuntimeException_CollectionNotFound e) {
LOGGER.debug("Collection '" + collection + "' not found.", e);
return Collections.emptyList();
}
}
return collectionLanguages;
}
@Override
public void close() {
// No finalization required.
}
public Record createCollectionInCurrentVersion(String code, String name, List<String> languages) {
return createCollectionInVersion(code, name, languages, null);
}
public Record createCollectionInCurrentVersion(String code, List<String> languages) {
return createCollectionInVersion(code, languages, null);
}
public Record createCollectionInVersion(String code, List<String> languages, String version) {
return createCollectionInVersion(code, code, languages, version);
}
public Record createCollectionInVersion(String code, String name, List<String> languages, String version) {
prepareCollectionCreationAndGetInvalidModules(code, name, languages, version);
return crateCollectionAfterPrepare(code, name, languages);
}
private Record crateCollectionAfterPrepare(String code, String name, List<String> languages) {
Record collectionRecord = createCollectionRecordWithCode(code, name, languages);
if (!code.equals(Collection.SYSTEM_COLLECTION)) {
addGlobalGroupsInCollection(code);
}
initializeCollection(code);
return collectionRecord;
}
private Set<String> prepareCollectionCreationAndGetInvalidModules(String code, String name,
List<String> languages, String version) {
validateCode(code);
boolean reindexingRequired = systemGlobalConfigsManager.isReindexingRequired();
if (collectionsListManager.getCollections().contains(code)) {
throw new CollectionsManagerRuntimeException_CollectionWithGivenCodeAlreadyExists(code);
}
String mainDataLanguage = modelLayerFactory.getConfiguration().getMainDataLanguage();
if (!languages.contains(mainDataLanguage)) {
throw new CollectionsManagerRuntimeException_CollectionLanguageMustIncludeSystemMainDataLanguage(
mainDataLanguage);
}
for (String language : languages) {
if (!Language.isSupported(language)) {
throw new CollectionsManagerRuntimeException_InvalidLanguage(language);
}
}
collectionLanguagesCache.put(code, languages);
createCollectionConfigs(code);
collectionsListManager.addCollection(code, languages);
Set<String> returnList = new HashSet<>();
try {
returnList.addAll(migrationServicesDelayed.get().migrate(code, version, true));
} catch (OptimisticLockingConfiguration optimisticLockingConfiguration) {
throw new CollectionsManagerRuntimeException_CannotMigrateCollection(code, version, optimisticLockingConfiguration);
} finally {
systemGlobalConfigsManager.setReindexingRequired(reindexingRequired);
}
return returnList;
}
public void validateCode(String code) {
if (!Collection.SYSTEM_COLLECTION.equals(code)) {
String pattern = "[a-zA-Z]([a-zA-Z0-9])+";
if (code == null || !code.matches(pattern)) {
throw new CollectionsManagerRuntimeException_InvalidCode(code);
}
}
}
public Set<String> initializeCollectionsAndGetInvalidModules() {
Set<String> returnList = new HashSet<>();
for (String collection : getCollectionCodes()) {
returnList.addAll(constellioModulesManager.startModules(collection));
initializeCollection(collection);
}
return returnList;
}
public void initializeModulesResources() {
Set<String> returnList = new HashSet<>();
for (String collection : getCollectionCodes()) {
constellioModulesManager.initializePluginResources(collection);
}
}
void initializeCollection(String collection) {
RecordsCache cache = modelLayerFactory.getRecordsCaches().getCache(collection);
SchemasRecordsServices core = new SchemasRecordsServices(collection, modelLayerFactory);
cache.configureCache(CacheConfig.permanentCache(core.userSchemaType()));
cache.configureCache(CacheConfig.permanentCache(core.groupSchemaType()));
cache.configureCache(CacheConfig.permanentCache(core.collectionSchemaType()));
cache.configureCache(CacheConfig.permanentCache(core.facetSchemaType()));
TaxonomiesManager taxonomiesManager = modelLayerFactory.getTaxonomiesManager();
MetadataSchemaTypes types = modelLayerFactory.getMetadataSchemasManager().getSchemaTypes(collection);
for (Taxonomy taxonomy : taxonomiesManager.getEnabledTaxonomies(collection)) {
for (String schemaType : taxonomy.getSchemaTypes()) {
cache.configureCache(CacheConfig.permanentCache(types.getSchemaType(schemaType)));
}
}
}
}