<commit_before>package ua.com.fielden.platform.web.centre;

import static java.lang.String.format;
import static java.util.Optional.ofNullable;
import static java.util.function.Function.identity;
import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.fetchKeyAndDescOnly;
import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.fetchKeyAndDescOnlyAndInstrument;
import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.from;
import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.select;
import static ua.com.fielden.platform.utils.CollectionUtil.setOf;
import static ua.com.fielden.platform.utils.EntityUtils.fetchWithKeyAndDesc;
import static ua.com.fielden.platform.web.centre.CentreDiffSerialiser.CENTRE_DIFF_SERIALISER;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;

import org.apache.log4j.Logger;

import ua.com.fielden.platform.domaintree.IDomainTreeEnhancerCache;
import ua.com.fielden.platform.domaintree.centre.ICentreDomainTreeManager.ICentreDomainTreeManagerAndEnhancer;
import ua.com.fielden.platform.domaintree.centre.impl.CentreDomainTreeManagerAndEnhancer;
import ua.com.fielden.platform.domaintree.impl.CalculatedPropertyInfo;
import ua.com.fielden.platform.domaintree.impl.CustomProperty;
import ua.com.fielden.platform.entity.factory.EntityFactory;
import ua.com.fielden.platform.entity.query.fluent.EntityQueryProgressiveInterfaces.ICompoundCondition0;
import ua.com.fielden.platform.entity.query.fluent.fetch;
import ua.com.fielden.platform.entity.query.model.EntityResultQueryModel;
import ua.com.fielden.platform.security.user.User;
import ua.com.fielden.platform.types.tuples.T2;
import ua.com.fielden.platform.ui.config.EntityCentreConfig;
import ua.com.fielden.platform.ui.config.MainMenuItem;
import ua.com.fielden.platform.ui.config.api.IEntityCentreConfig;
import ua.com.fielden.platform.ui.config.api.IMainMenuItem;
import ua.com.fielden.platform.ui.menu.MiWithConfigurationSupport;
import ua.com.fielden.platform.web.app.IWebUiConfig;
import ua.com.fielden.platform.web.interfaces.DeviceProfile;

/**
 * This utility class contains additional methods applicable to {@link CentreUpdater}.
 *
 * @author TG Team
 *
 */
public class CentreUpdaterUtils extends CentreUpdater {
    private final static Logger logger = Logger.getLogger(CentreUpdaterUtils.class);
    public static final fetch<EntityCentreConfig> FETCH_CONFIG_AND_INSTRUMENT = fetchKeyAndDescOnlyAndInstrument(EntityCentreConfig.class);
    public static final fetch<EntityCentreConfig> FETCH_CONFIG = fetchKeyAndDescOnly(EntityCentreConfig.class);
    
    /** Protected default constructor to prevent instantiation. */
    protected CentreUpdaterUtils() {
    }
    
    ///////////////////////////// CENTRE CREATION /////////////////////////////
    /**
     * Creates default centre for concrete menu item type. Looks for Centre DSL config in {@link IWebUiConfig}.
     * 
     * @param menuItemType
     * @param webUiConfig
     * @return
     */
    protected static ICentreDomainTreeManagerAndEnhancer createDefaultCentre(final Class<?> menuItemType, final IWebUiConfig webUiConfig) {
        final EntityCentre<?> entityCentre = webUiConfig.getCentres().get(menuItemType);
        if (entityCentre != null) {
            return entityCentre.createDefaultCentre();
        } else {
            throw new CentreUpdaterException(format("EntityCentre instance could not be found for [%s] menu item type.", menuItemType.getSimpleName()));
        }
    }
    
    /**
     * Creates empty centre manager with calculated and custom properties.
     * 
     * @param root
     * @param entityFactory
     * @param domainTreeEnhancerCache
     * @param calculatedAndCustomProperties
     * @param miType
     * @return
     */
    public static ICentreDomainTreeManagerAndEnhancer createEmptyCentre(final Class<?> root, final EntityFactory entityFactory, final IDomainTreeEnhancerCache domainTreeEnhancerCache, final T2<Map<Class<?>, Set<CalculatedPropertyInfo>>, Map<Class<?>, List<CustomProperty>>> calculatedAndCustomProperties, final Class<? extends MiWithConfigurationSupport<?>> miType) {
        final CentreDomainTreeManagerAndEnhancer centre = new CentreDomainTreeManagerAndEnhancer(entityFactory, domainTreeEnhancerCache, setOf(root), calculatedAndCustomProperties, miType);
        // initialise checkedProperties tree to make it more predictable in getting meta-info from "checkedProperties"
        centre.getFirstTick().checkedProperties(root);
        centre.getSecondTick().checkedProperties(root);
        return centre;
    }
    
    ///////////////////////////// CENTRE MAINTENANCE /////////////////////////////
    /**
     * Retrieves diff instance from database if there is any.
     * Restores it from binary representation.
     * 
     * @param menuItemType
     * @param user
     * @param name
     * @param eccCompanion
     * @return
     */
    public static Optional<Map<String, Object>> retrieveDiff(
            final Class<?> menuItemType,
            final User user,
            final String name,
            final IEntityCentreConfig eccCompanion) {
        return ofNullable(eccCompanion.getEntity(from(modelFor(user, menuItemType.getName(), name)).model()))
                .map(ecc -> restoreDiffFrom(ecc, eccCompanion, format("for type [%s] with name [%s] for user [%s]", menuItemType.getSimpleName(), name, user)));
    }
    
    /**
     * Restores diff instance from {@link EntityCentreConfig} instance's binary data.
     * In case of failure, initialises new empty instance, saves it and returns as a result.
     * 
     * @param ecc
     * @param eccCompanion
     * @param loggingSuffix
     * @return
     */
    private static Map<String, Object> restoreDiffFrom(
            // params for actual deserialisation
            final EntityCentreConfig ecc,
            // params for: deserialisation failed -- create empty and save
            final IEntityCentreConfig eccCompanion,
            // params for: deserialisation failed -- logging
            final String loggingSuffix) {
        try {
            return CENTRE_DIFF_SERIALISER.deserialise(ecc.getConfigBody());
        } catch (final Exception deserialisationException) {
            logger.error("============================================ CENTRE DESERIALISATION HAS FAILED ============================================");
            logger.error(format("Unable to deserialise entity centre instance %s. Exception:", loggingSuffix), deserialisationException);
            logger.error(format("Creating and saving of empty diff %s...", loggingSuffix));
            final Map<String, Object> emptyDiff = createEmptyDifferences();
            ecc.setConfigBody(CENTRE_DIFF_SERIALISER.serialise(emptyDiff));
            eccCompanion.saveWithConflicts(ecc); // this rare saving case should never be conflicted -- it is safe to use saveWithConflicts here (also nesting inside bigger transaction scopes is required)
            logger.error(format("Creating and saving of empty diff %s...done", loggingSuffix));
            logger.error("============================================ CENTRE DESERIALISATION HAS FAILED [END] ============================================");
            return emptyDiff;
        }
    }
    
    /**
     * Saves new {@link EntityCentreConfig} instance with serialised diff inside.
     */
    public static Map<String, Object> saveNewEntityCentreManager(
        final Map<String, Object> differences,
        final Class<?> menuItemType,
        final User user,
        final String newName,
        final String newDesc,
        final IEntityCentreConfig eccCompanion,
        final IMainMenuItem mmiCompanion
    ) {
        return saveNewEntityCentreManager(false, differences, menuItemType, user, newName, newDesc, eccCompanion, mmiCompanion);
    }
    
    /**
     * Saves new {@link EntityCentreConfig} instance with serialised {@code differences} inside.
     * 
     * @param withoutConflicts -- <code>true</code> to avoid self-conflict checks, <code>false</code> otherwise; <code>true</code> only to be used NOT IN another SessionRequired transaction scope
     */
    public static Map<String, Object> saveNewEntityCentreManager(
        final boolean withoutConflicts,
        final Map<String, Object> differences,
        final Class<?> menuItemType,
        final User user,
        final String newName,
        final String newDesc,
        final IEntityCentreConfig eccCompanion,
        final IMainMenuItem mmiCompanion
    ) {
        saveNewEntityCentreManager(withoutConflicts, CENTRE_DIFF_SERIALISER.serialise(differences), menuItemType, user, newName, newDesc, eccCompanion, mmiCompanion, identity());
        return differences;
    }

    
    /**
     * Saves new {@link EntityCentreConfig} instance with {@code serialisedDifferences} inside.
     * 
     * @param withoutConflicts -- <code>true</code> to avoid self-conflict checks, <code>false</code> otherwise; <code>true</code> only to be used NOT IN another SessionRequired transaction scope
     * @param adjustConfig -- function to adjust newly created centre config just before saving
     */
    public static Long saveNewEntityCentreManager(
        final boolean withoutConflicts,
        final byte[] serialisedDifferences,
        final Class<?> menuItemType,
        final User user,
        final String newName,
        final String newDesc,
        final IEntityCentreConfig eccCompanion,
        final IMainMenuItem mmiCompanion,
        final Function<EntityCentreConfig, EntityCentreConfig> adjustConfig
    ) {
        final MainMenuItem menuItem = mmiCompanion.findByKeyOptional(menuItemType.getName()).orElseGet(() -> {
            final MainMenuItem newMainMenuItem = mmiCompanion.new_();
            newMainMenuItem.setKey(menuItemType.getName());
            return mmiCompanion.save(newMainMenuItem);
        });
        final EntityCentreConfig ecc = eccCompanion.new_().setOwner(user).setTitle(newName).setMenuItem(menuItem).setConfigBody(serialisedDifferences).setDesc(newDesc);
        if (withoutConflicts) {
            return eccCompanion.saveWithoutConflicts(ecc);
        } else {
            return eccCompanion.saveWithConflicts(ecc);
        }
    }
    
    /**
     * Overrides existing {@link EntityCentreConfig} instance with new serialised diff.
     * Otherwise, in case where there is no such instance in database, creates and saves new {@link EntityCentreConfig} instance with serialised diff inside.
     * 
     * @param withoutConflicts -- <code>true</code> to avoid self-conflict checks, <code>false</code> otherwise; <code>true</code> only to be used NOT IN another SessionRequired transaction scope
     */
    public static Map<String, Object> saveEntityCentreManager(<commit_msg>#1534 Fixed omission that has made first-time inherited from shared loading broken (was loading into own save-as).<commit_after>package ua.com.fielden.platform.web.centre;

import static java.lang.String.format;
import static java.util.Optional.ofNullable;
import static java.util.function.Function.identity;
import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.fetchKeyAndDescOnly;
import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.fetchKeyAndDescOnlyAndInstrument;
import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.from;
import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.select;
import static ua.com.fielden.platform.utils.CollectionUtil.setOf;
import static ua.com.fielden.platform.utils.EntityUtils.fetchWithKeyAndDesc;
import static ua.com.fielden.platform.web.centre.CentreDiffSerialiser.CENTRE_DIFF_SERIALISER;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;

import org.apache.log4j.Logger;

import ua.com.fielden.platform.domaintree.IDomainTreeEnhancerCache;
import ua.com.fielden.platform.domaintree.centre.ICentreDomainTreeManager.ICentreDomainTreeManagerAndEnhancer;
import ua.com.fielden.platform.domaintree.centre.impl.CentreDomainTreeManagerAndEnhancer;
import ua.com.fielden.platform.domaintree.impl.CalculatedPropertyInfo;
import ua.com.fielden.platform.domaintree.impl.CustomProperty;
import ua.com.fielden.platform.entity.factory.EntityFactory;
import ua.com.fielden.platform.entity.query.fluent.EntityQueryProgressiveInterfaces.ICompoundCondition0;
import ua.com.fielden.platform.entity.query.fluent.fetch;
import ua.com.fielden.platform.entity.query.model.EntityResultQueryModel;
import ua.com.fielden.platform.security.user.User;
import ua.com.fielden.platform.types.tuples.T2;
import ua.com.fielden.platform.ui.config.EntityCentreConfig;
import ua.com.fielden.platform.ui.config.MainMenuItem;
import ua.com.fielden.platform.ui.config.api.IEntityCentreConfig;
import ua.com.fielden.platform.ui.config.api.IMainMenuItem;
import ua.com.fielden.platform.ui.menu.MiWithConfigurationSupport;
import ua.com.fielden.platform.web.app.IWebUiConfig;
import ua.com.fielden.platform.web.interfaces.DeviceProfile;

/**
 * This utility class contains additional methods applicable to {@link CentreUpdater}.
 *
 * @author TG Team
 *
 */
public class CentreUpdaterUtils extends CentreUpdater {
    private final static Logger logger = Logger.getLogger(CentreUpdaterUtils.class);
    public static final fetch<EntityCentreConfig> FETCH_CONFIG_AND_INSTRUMENT = fetchKeyAndDescOnlyAndInstrument(EntityCentreConfig.class);
    public static final fetch<EntityCentreConfig> FETCH_CONFIG = fetchKeyAndDescOnly(EntityCentreConfig.class);
    
    /** Protected default constructor to prevent instantiation. */
    protected CentreUpdaterUtils() {
    }
    
    ///////////////////////////// CENTRE CREATION /////////////////////////////
    /**
     * Creates default centre for concrete menu item type. Looks for Centre DSL config in {@link IWebUiConfig}.
     * 
     * @param menuItemType
     * @param webUiConfig
     * @return
     */
    protected static ICentreDomainTreeManagerAndEnhancer createDefaultCentre(final Class<?> menuItemType, final IWebUiConfig webUiConfig) {
        final EntityCentre<?> entityCentre = webUiConfig.getCentres().get(menuItemType);
        if (entityCentre != null) {
            return entityCentre.createDefaultCentre();
        } else {
            throw new CentreUpdaterException(format("EntityCentre instance could not be found for [%s] menu item type.", menuItemType.getSimpleName()));
        }
    }
    
    /**
     * Creates empty centre manager with calculated and custom properties.
     * 
     * @param root
     * @param entityFactory
     * @param domainTreeEnhancerCache
     * @param calculatedAndCustomProperties
     * @param miType
     * @return
     */
    public static ICentreDomainTreeManagerAndEnhancer createEmptyCentre(final Class<?> root, final EntityFactory entityFactory, final IDomainTreeEnhancerCache domainTreeEnhancerCache, final T2<Map<Class<?>, Set<CalculatedPropertyInfo>>, Map<Class<?>, List<CustomProperty>>> calculatedAndCustomProperties, final Class<? extends MiWithConfigurationSupport<?>> miType) {
        final CentreDomainTreeManagerAndEnhancer centre = new CentreDomainTreeManagerAndEnhancer(entityFactory, domainTreeEnhancerCache, setOf(root), calculatedAndCustomProperties, miType);
        // initialise checkedProperties tree to make it more predictable in getting meta-info from "checkedProperties"
        centre.getFirstTick().checkedProperties(root);
        centre.getSecondTick().checkedProperties(root);
        return centre;
    }
    
    ///////////////////////////// CENTRE MAINTENANCE /////////////////////////////
    /**
     * Retrieves diff instance from database if there is any.
     * Restores it from binary representation.
     * 
     * @param menuItemType
     * @param user
     * @param name
     * @param eccCompanion
     * @return
     */
    public static Optional<Map<String, Object>> retrieveDiff(
            final Class<?> menuItemType,
            final User user,
            final String name,
            final IEntityCentreConfig eccCompanion) {
        return ofNullable(eccCompanion.getEntity(from(modelFor(user, menuItemType.getName(), name)).model()))
                .map(ecc -> restoreDiffFrom(ecc, eccCompanion, format("for type [%s] with name [%s] for user [%s]", menuItemType.getSimpleName(), name, user)));
    }
    
    /**
     * Restores diff instance from {@link EntityCentreConfig} instance's binary data.
     * In case of failure, initialises new empty instance, saves it and returns as a result.
     * 
     * @param ecc
     * @param eccCompanion
     * @param loggingSuffix
     * @return
     */
    private static Map<String, Object> restoreDiffFrom(
            // params for actual deserialisation
            final EntityCentreConfig ecc,
            // params for: deserialisation failed -- create empty and save
            final IEntityCentreConfig eccCompanion,
            // params for: deserialisation failed -- logging
            final String loggingSuffix) {
        try {
            return CENTRE_DIFF_SERIALISER.deserialise(ecc.getConfigBody());
        } catch (final Exception deserialisationException) {
            logger.error("============================================ CENTRE DESERIALISATION HAS FAILED ============================================");
            logger.error(format("Unable to deserialise entity centre instance %s. Exception:", loggingSuffix), deserialisationException);
            logger.error(format("Creating and saving of empty diff %s...", loggingSuffix));
            final Map<String, Object> emptyDiff = createEmptyDifferences();
            ecc.setConfigBody(CENTRE_DIFF_SERIALISER.serialise(emptyDiff));
            eccCompanion.saveWithConflicts(ecc); // this rare saving case should never be conflicted -- it is safe to use saveWithConflicts here (also nesting inside bigger transaction scopes is required)
            logger.error(format("Creating and saving of empty diff %s...done", loggingSuffix));
            logger.error("============================================ CENTRE DESERIALISATION HAS FAILED [END] ============================================");
            return emptyDiff;
        }
    }
    
    /**
     * Saves new {@link EntityCentreConfig} instance with serialised diff inside.
     */
    public static Map<String, Object> saveNewEntityCentreManager(
        final Map<String, Object> differences,
        final Class<?> menuItemType,
        final User user,
        final String newName,
        final String newDesc,
        final IEntityCentreConfig eccCompanion,
        final IMainMenuItem mmiCompanion
    ) {
        return saveNewEntityCentreManager(false, differences, menuItemType, user, newName, newDesc, eccCompanion, mmiCompanion);
    }
    
    /**
     * Saves new {@link EntityCentreConfig} instance with serialised {@code differences} inside.
     * 
     * @param withoutConflicts -- <code>true</code> to avoid self-conflict checks, <code>false</code> otherwise; <code>true</code> only to be used NOT IN another SessionRequired transaction scope
     */
    public static Map<String, Object> saveNewEntityCentreManager(
        final boolean withoutConflicts,
        final Map<String, Object> differences,
        final Class<?> menuItemType,
        final User user,
        final String newName,
        final String newDesc,
        final IEntityCentreConfig eccCompanion,
        final IMainMenuItem mmiCompanion
    ) {
        saveNewEntityCentreManager(withoutConflicts, CENTRE_DIFF_SERIALISER.serialise(differences), menuItemType, user, newName, newDesc, eccCompanion, mmiCompanion, identity());
        return differences;
    }

    
    /**
     * Saves new {@link EntityCentreConfig} instance with {@code serialisedDifferences} inside.
     * 
     * @param withoutConflicts -- <code>true</code> to avoid self-conflict checks, <code>false</code> otherwise; <code>true</code> only to be used NOT IN another SessionRequired transaction scope
     * @param adjustConfig -- function to adjust newly created centre config just before saving
     */
    public static Long saveNewEntityCentreManager(
        final boolean withoutConflicts,
        final byte[] serialisedDifferences,
        final Class<?> menuItemType,
        final User user,
        final String newName,
        final String newDesc,
        final IEntityCentreConfig eccCompanion,
        final IMainMenuItem mmiCompanion,
        final Function<EntityCentreConfig, EntityCentreConfig> adjustConfig
    ) {
        final MainMenuItem menuItem = mmiCompanion.findByKeyOptional(menuItemType.getName()).orElseGet(() -> {
            final MainMenuItem newMainMenuItem = mmiCompanion.new_();
            newMainMenuItem.setKey(menuItemType.getName());
            return mmiCompanion.save(newMainMenuItem);
        });
        final EntityCentreConfig ecc = adjustConfig.apply(eccCompanion.new_().setOwner(user).setTitle(newName).setMenuItem(menuItem).setConfigBody(serialisedDifferences).setDesc(newDesc));
        if (withoutConflicts) {
            return eccCompanion.saveWithoutConflicts(ecc);
        } else {
            return eccCompanion.saveWithConflicts(ecc);
        }
    }
    
    /**
     * Overrides existing {@link EntityCentreConfig} instance with new serialised diff.
     * Otherwise, in case where there is no such instance in database, creates and saves new {@link EntityCentreConfig} instance with serialised diff inside.
     * 
     * @param withoutConflicts -- <code>true</code> to avoid self-conflict checks, <code>false</code> otherwise; <code>true</code> only to be used NOT IN another SessionRequired transaction scope
     */
    public static Map<String, Object> saveEntityCentreManager(
{'fraction_non_alphanumeric': 0.11156667122226778, 'fraction_numerical': 0.0005466721333880005, 'mean_word_length': 3.5085233107414253, 'pattern_counts': {'":': 0, '<': 95, '<?xml version=': 0, '>': 99, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2020254', 'n_tokens_mistral': 5971, 'n_tokens_neox': 5857, 'n_words': 1445}