<commit_before>package ua.com.fielden.platform.attachment;

import static java.lang.String.format;
import static ua.com.fielden.platform.attachment.Attachment.pn_LAST_MODIFIED;
import static ua.com.fielden.platform.attachment.Attachment.pn_LAST_REVISION;
import static ua.com.fielden.platform.attachment.Attachment.pn_MIME;
import static ua.com.fielden.platform.attachment.Attachment.pn_ORIG_FILE_NAME;
import static ua.com.fielden.platform.attachment.Attachment.pn_PREV_REVISION;
import static ua.com.fielden.platform.attachment.Attachment.pn_REV_NO;
import static ua.com.fielden.platform.attachment.Attachment.pn_SHA1;
import static ua.com.fielden.platform.attachment.Attachment.pn_TITLE;
import static ua.com.fielden.platform.error.Result.failure;
import static ua.com.fielden.platform.error.Result.successful;
import static ua.com.fielden.platform.utils.CollectionUtil.setOf;
import static ua.com.fielden.platform.utils.EntityUtils.equalsEx;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.IOUtils;

import com.google.inject.Inject;
import com.google.inject.name.Named;

import ua.com.fielden.platform.attachment.validators.CanBeUsedAsPrevAttachmentRev;
import ua.com.fielden.platform.dao.CommonEntityDao;
import ua.com.fielden.platform.dao.annotations.AfterSave;
import ua.com.fielden.platform.dao.annotations.SessionRequired;
import ua.com.fielden.platform.dao.handlers.IAttachmentAfterSave;
import ua.com.fielden.platform.entity.annotation.EntityType;
import ua.com.fielden.platform.entity.factory.EntityFactory;
import ua.com.fielden.platform.entity.fetch.IFetchProvider;
import ua.com.fielden.platform.entity.query.IFilter;
import ua.com.fielden.platform.entity.query.model.EntityResultQueryModel;
import ua.com.fielden.platform.error.Result;

@EntityType(Attachment.class)
@AfterSave(IAttachmentAfterSave.class)
public class AttachmentDao extends CommonEntityDao<Attachment> implements IAttachment {

    private final String attachmentsLocation;

    @Inject
    protected AttachmentDao(
            final IFilter filter,
            final EntityFactory factory,
            final @Named("attachments.location") String attachmentsLocation) {
        super(filter);
        this.attachmentsLocation = attachmentsLocation;
    }

    @Override
    @SessionRequired
    public Attachment save(final Attachment attachment) {
        attachment.isValid().ifFailure(Result::throwRuntime);

        // check if prev. revision was specified
        // this would indicate that document revision process is at play...
        final boolean revisionHistoryModified = attachment.getPrevRevision() != null && attachment.getProperty(pn_PREV_REVISION).isDirty();
        
        final Attachment savedAttachment = super.save(attachment);
        
        if (revisionHistoryModified) {
            updateAttachmentRevisionHistory(savedAttachment).ifFailure(Result::throwRuntime);
            return findByEntityAndFetch(getFetchProvider().fetchModel(), savedAttachment);
        } else {        
            return savedAttachment;
        }
    }
    
    /**
     * Ensures correct revision history, including revision numbering and references.
     * 
     * @param savedAttachment
     * @param revNoIncBy 
     * @return
     */
    private Result updateAttachmentRevisionHistory(final Attachment savedAttachment) {
        if (savedAttachment.isDirty()) {
            return failure("Attachment revision history can only be updated for persisted attachments.");
        }
        
        // if last revision is not populated or references itself then the saved attachment represents the latest revision
        // this means that prev. revision needs to have its last revision updated
        if (savedAttachment.getLastRevision() == null || equalsEx(savedAttachment, savedAttachment.getLastRevision())) {
            final Attachment prevRev = findByEntityAndFetch(getFetchProvider().fetchModel(), savedAttachment.getPrevRevision());
            super.save(prevRev.setLastRevision(savedAttachment));
            super.save(savedAttachment.setLastRevision(savedAttachment));
            return Result.successful(savedAttachment);
        } else { // otherwise, this is the case of joining two revision histories or the case of updating the history from the tail-end -- both are handled identically
            final Attachment lastRev = co(Attachment.class).findByEntityAndFetch(getFetchProvider().fetchModel(), savedAttachment.getLastRevision());
            return traverseAndUpdateHistory(lastRev, lastRev, /* sha1Checksums = */ setOf());
        }
    }
    
    private Result traverseAndUpdateHistory(final Attachment lastRev, final Attachment tracedRevision, final Set<String> sha1Checksums) {
        if (tracedRevision == null) {
            return successful(tracedRevision);
        } else {
            if (sha1Checksums.contains(tracedRevision.getSha1())) {
                return failure(format(CanBeUsedAsPrevAttachmentRev.ERR_DUPLICATE_SHA1, tracedRevision.getSha1()));
            }
            sha1Checksums.add(tracedRevision.getSha1());
            
            final Result res = traverseAndUpdateHistory(lastRev, tracedRevision.getPrevRevision() != null ? co(Attachment.class).findByEntityAndFetch(getFetchProvider().fetchModel(), tracedRevision.getPrevRevision()) : null, sha1Checksums);
            if (!res.isSuccessful()) {
                return res;
            }
            
            try {
                final Attachment attachmentToUpdate = findByEntityAndFetch(getFetchProvider().fetchModel(), tracedRevision);
                if (attachmentToUpdate.getPrevRevision() != null) {
                    attachmentToUpdate.setRevNo(attachmentToUpdate.getPrevRevision().getRevNo() + 1);
                }
                attachmentToUpdate.beginLastRevisionUpdate().setLastRevision(lastRev).endLastRevisionUpdate();
                
                return successful(super.save(attachmentToUpdate));
            } catch (final Result ex) {
                return ex;
            } catch (final Exception ex) {
                return failure(ex);
            }
        }
    }

    @Override
    protected IFetchProvider<Attachment> createFetchProvider() {
        return super.createFetchProvider().with(
                pn_TITLE, pn_SHA1, pn_ORIG_FILE_NAME, pn_REV_NO, 
                pn_PREV_REVISION, pn_PREV_REVISION + "." + pn_REV_NO, 
                pn_LAST_REVISION, pn_LAST_MODIFIED, pn_MIME);
    }
    
    @Override
    public Attachment new_() {
        return super.new_().setRevNo(0);
    }<commit_msg>#539 Adjusted the default Attachment fetch model for editing on its master.<commit_after>package ua.com.fielden.platform.attachment;

import static java.lang.String.format;
import static ua.com.fielden.platform.attachment.Attachment.pn_LAST_MODIFIED;
import static ua.com.fielden.platform.attachment.Attachment.pn_LAST_REVISION;
import static ua.com.fielden.platform.attachment.Attachment.pn_MIME;
import static ua.com.fielden.platform.attachment.Attachment.pn_ORIG_FILE_NAME;
import static ua.com.fielden.platform.attachment.Attachment.pn_PREV_REVISION;
import static ua.com.fielden.platform.attachment.Attachment.pn_REV_NO;
import static ua.com.fielden.platform.attachment.Attachment.pn_SHA1;
import static ua.com.fielden.platform.attachment.Attachment.pn_TITLE;
import static ua.com.fielden.platform.entity.AbstractEntity.DESC;
import static ua.com.fielden.platform.error.Result.failure;
import static ua.com.fielden.platform.error.Result.successful;
import static ua.com.fielden.platform.utils.CollectionUtil.setOf;
import static ua.com.fielden.platform.utils.EntityUtils.equalsEx;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.IOUtils;

import com.google.inject.Inject;
import com.google.inject.name.Named;

import ua.com.fielden.platform.attachment.validators.CanBeUsedAsPrevAttachmentRev;
import ua.com.fielden.platform.dao.CommonEntityDao;
import ua.com.fielden.platform.dao.annotations.AfterSave;
import ua.com.fielden.platform.dao.annotations.SessionRequired;
import ua.com.fielden.platform.dao.handlers.IAttachmentAfterSave;
import ua.com.fielden.platform.entity.AbstractEntity;
import ua.com.fielden.platform.entity.annotation.EntityType;
import ua.com.fielden.platform.entity.factory.EntityFactory;
import ua.com.fielden.platform.entity.fetch.IFetchProvider;
import ua.com.fielden.platform.entity.query.IFilter;
import ua.com.fielden.platform.entity.query.model.EntityResultQueryModel;
import ua.com.fielden.platform.error.Result;

@EntityType(Attachment.class)
@AfterSave(IAttachmentAfterSave.class)
public class AttachmentDao extends CommonEntityDao<Attachment> implements IAttachment {

    private final String attachmentsLocation;

    @Inject
    protected AttachmentDao(
            final IFilter filter,
            final EntityFactory factory,
            final @Named("attachments.location") String attachmentsLocation) {
        super(filter);
        this.attachmentsLocation = attachmentsLocation;
    }

    @Override
    @SessionRequired
    public Attachment save(final Attachment attachment) {
        attachment.isValid().ifFailure(Result::throwRuntime);

        // check if prev. revision was specified
        // this would indicate that document revision process is at play...
        final boolean revisionHistoryModified = attachment.getPrevRevision() != null && attachment.getProperty(pn_PREV_REVISION).isDirty();
        
        final Attachment savedAttachment = super.save(attachment);
        
        if (revisionHistoryModified) {
            updateAttachmentRevisionHistory(savedAttachment).ifFailure(Result::throwRuntime);
            return findByEntityAndFetch(getFetchProvider().fetchModel(), savedAttachment);
        } else {        
            return savedAttachment;
        }
    }
    
    /**
     * Ensures correct revision history, including revision numbering and references.
     * 
     * @param savedAttachment
     * @param revNoIncBy 
     * @return
     */
    private Result updateAttachmentRevisionHistory(final Attachment savedAttachment) {
        if (savedAttachment.isDirty()) {
            return failure("Attachment revision history can only be updated for persisted attachments.");
        }
        
        // if last revision is not populated or references itself then the saved attachment represents the latest revision
        // this means that prev. revision needs to have its last revision updated
        if (savedAttachment.getLastRevision() == null || equalsEx(savedAttachment, savedAttachment.getLastRevision())) {
            final Attachment prevRev = findByEntityAndFetch(getFetchProvider().fetchModel(), savedAttachment.getPrevRevision());
            super.save(prevRev.setLastRevision(savedAttachment));
            super.save(savedAttachment.setLastRevision(savedAttachment));
            return Result.successful(savedAttachment);
        } else { // otherwise, this is the case of joining two revision histories or the case of updating the history from the tail-end -- both are handled identically
            final Attachment lastRev = co(Attachment.class).findByEntityAndFetch(getFetchProvider().fetchModel(), savedAttachment.getLastRevision());
            return traverseAndUpdateHistory(lastRev, lastRev, /* sha1Checksums = */ setOf());
        }
    }
    
    private Result traverseAndUpdateHistory(final Attachment lastRev, final Attachment tracedRevision, final Set<String> sha1Checksums) {
        if (tracedRevision == null) {
            return successful(tracedRevision);
        } else {
            if (sha1Checksums.contains(tracedRevision.getSha1())) {
                return failure(format(CanBeUsedAsPrevAttachmentRev.ERR_DUPLICATE_SHA1, tracedRevision.getSha1()));
            }
            sha1Checksums.add(tracedRevision.getSha1());
            
            final Result res = traverseAndUpdateHistory(lastRev, tracedRevision.getPrevRevision() != null ? co(Attachment.class).findByEntityAndFetch(getFetchProvider().fetchModel(), tracedRevision.getPrevRevision()) : null, sha1Checksums);
            if (!res.isSuccessful()) {
                return res;
            }
            
            try {
                final Attachment attachmentToUpdate = findByEntityAndFetch(getFetchProvider().fetchModel(), tracedRevision);
                if (attachmentToUpdate.getPrevRevision() != null) {
                    attachmentToUpdate.setRevNo(attachmentToUpdate.getPrevRevision().getRevNo() + 1);
                }
                attachmentToUpdate.beginLastRevisionUpdate().setLastRevision(lastRev).endLastRevisionUpdate();
                
                return successful(super.save(attachmentToUpdate));
            } catch (final Result ex) {
                return ex;
            } catch (final Exception ex) {
                return failure(ex);
            }
        }
    }

    @Override
    protected IFetchProvider<Attachment> createFetchProvider() {
        return super.createFetchProvider().with(
                DESC,
                pn_TITLE, pn_SHA1, pn_ORIG_FILE_NAME, pn_REV_NO, 
                pn_PREV_REVISION, pn_PREV_REVISION + "." + pn_REV_NO, pn_PREV_REVISION + "." + pn_LAST_REVISION, 
                pn_LAST_REVISION, pn_LAST_MODIFIED, pn_MIME);
    }
    
    @Override
    public Attachment new_() {
        return super.new_().setRevNo(0);
    }
{'fraction_non_alphanumeric': 0.09062201659690093, 'fraction_numerical': 0.002129690827641918, 'mean_word_length': 3.3067678684376975, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 9, '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': '537012', 'n_tokens_mistral': 3727, 'n_tokens_neox': 3681, 'n_words': 715}