001/*
002 * (C) Copyright 2018 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *    http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Kevin Leturc <kleturc@nuxeo.com>
018 */
019package org.nuxeo.ecm.core.trash;
020
021import static org.nuxeo.ecm.core.query.sql.NXQL.ECM_UUID;
022import static org.nuxeo.ecm.core.trash.PropertyTrashService.SYSPROP_IS_TRASHED;
023
024import java.io.Serializable;
025import java.util.List;
026import java.util.Map;
027import java.util.stream.Collectors;
028
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031import org.nuxeo.ecm.core.api.CoreInstance;
032import org.nuxeo.ecm.core.api.CoreSession;
033import org.nuxeo.ecm.core.api.IdRef;
034import org.nuxeo.ecm.core.repository.RepositoryService;
035import org.nuxeo.runtime.api.Framework;
036import org.nuxeo.runtime.migration.MigrationService.MigrationContext;
037import org.nuxeo.runtime.migration.MigrationService.Migrator;
038import org.nuxeo.runtime.transaction.TransactionHelper;
039
040/**
041 * Migrator of trashed state from lifecycle to property.
042 *
043 * @since 10.2
044 */
045public class TrashedStateLifeCycleToPropertyMigrator implements Migrator {
046
047    private static final Log log = LogFactory.getLog(TrashedStateLifeCycleToPropertyMigrator.class);
048
049    protected static final int BATCH_SIZE = 50;
050
051    protected MigrationContext migrationContext;
052
053    @Override
054    public void run(MigrationContext migrationContext) {
055        this.migrationContext = migrationContext;
056        reportProgress("Initializing", 0, -1); // unknown
057        List<String> repositoryNames = Framework.getService(RepositoryService.class).getRepositoryNames();
058        try {
059            repositoryNames.forEach(this::migrateRepository);
060            reportProgress("Done", 1, 1);
061        } catch (MigrationShutdownException e) {
062            return;
063        }
064    }
065
066    protected void checkShutdownRequested() {
067        if (migrationContext.isShutdownRequested()) {
068            throw new MigrationShutdownException();
069        }
070    }
071
072    protected void reportProgress(String message, long num, long total) {
073        log.debug(message + ": " + num + "/" + total);
074        migrationContext.reportProgress(message, num, total);
075    }
076
077    protected void reportProgress(String repositoryName, String message, long num, long total) {
078        reportProgress(String.format("[%s] %s", repositoryName, message), num, total);
079    }
080
081    protected void migrateRepository(String repositoryName) {
082        reportProgress(repositoryName, "Initializing", 0, -1); // unknown
083        TransactionHelper.runInTransaction(() -> CoreInstance.doPrivileged(repositoryName, this::migrateSession));
084    }
085
086    protected void migrateSession(CoreSession session) {
087        // query all 'deleted' documents
088        String deletedQuery = "SELECT ecm:uuid FROM Document WHERE ecm:currentLifeCycleState = 'deleted' "
089                + "AND ecm:isVersion = 0";
090        List<Map<String, Serializable>> deletedMaps = session.queryProjection(deletedQuery, -1, 0);
091
092        checkShutdownRequested();
093
094        // compute all deleted doc id refs
095        List<IdRef> deletedRefs = deletedMaps.stream() //
096                                             .map(map -> (String) map.get(ECM_UUID))
097                                             .map(IdRef::new)
098                                             .collect(Collectors.toList());
099
100        checkShutdownRequested();
101
102        // set ecm:isTrashed property by batch
103        int size = deletedRefs.size();
104        int i = 0;
105        reportProgress(session.getRepositoryName(), "Setting isTrashed property", i, size);
106        for (IdRef deletedRef : deletedRefs) {
107            session.setDocumentSystemProp(deletedRef, SYSPROP_IS_TRASHED, Boolean.TRUE);
108            checkShutdownRequested();
109            i++;
110            if (i % BATCH_SIZE == 0 || i == size) {
111                reportProgress(session.getRepositoryName(), "Setting isTrashed property", i, size);
112                TransactionHelper.commitOrRollbackTransaction();
113                TransactionHelper.startTransaction();
114            }
115        }
116        reportProgress(session.getRepositoryName(), "Done", size, size);
117    }
118
119    // exception used for simpler flow control
120    protected static class MigrationShutdownException extends RuntimeException {
121
122        private static final long serialVersionUID = 1L;
123
124        public MigrationShutdownException() {
125            super();
126        }
127    }
128}