001/*
002 * (C) Copyright 2015-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 *     Thomas Roger
018 */
019
020package org.nuxeo.ecm.platform.picture;
021
022import static org.apache.commons.logging.LogFactory.getLog;
023import static org.nuxeo.ecm.core.api.CoreSession.ALLOW_VERSION_WRITE;
024import static org.nuxeo.ecm.core.query.sql.NXQL.ECM_UUID;
025import static org.nuxeo.ecm.core.query.sql.NXQL.NXQL;
026
027import java.io.Serializable;
028import java.util.HashSet;
029import java.util.Map;
030import java.util.Set;
031
032import org.apache.commons.lang3.StringUtils;
033import org.apache.commons.logging.Log;
034import org.nuxeo.ecm.core.api.Blob;
035import org.nuxeo.ecm.core.api.CoreSession;
036import org.nuxeo.ecm.core.api.DocumentModel;
037import org.nuxeo.ecm.core.api.IdRef;
038import org.nuxeo.ecm.core.api.IterableQueryResult;
039import org.nuxeo.ecm.core.repository.RepositoryInitializationHandler;
040import org.nuxeo.ecm.platform.picture.api.PictureView;
041import org.nuxeo.ecm.platform.picture.api.adapters.MultiviewPicture;
042import org.nuxeo.runtime.api.Framework;
043import org.nuxeo.runtime.services.config.ConfigurationService;
044import org.nuxeo.runtime.transaction.TransactionHelper;
045
046/**
047 * Migrate old Original picture view to {@code file:content}.
048 * <p>
049 * Does not copy it if {@code file:content} is not empty. When done, the Original picture view is removed.
050 * <p>
051 * It does not recompute the picture views.
052 *
053 * @since 7.2
054 */
055public class PictureMigrationHandler extends RepositoryInitializationHandler {
056
057    private static final Log log = getLog(PictureMigrationHandler.class);
058
059    public static final String PICTURES_TO_MIGRATE_QUERY = "SELECT ecm:uuid FROM Document "
060            + "WHERE ecm:mixinType = 'Picture' AND ecm:isProxy = 0 AND views/*/title = 'Original' "
061            + "AND content/data IS NULL";
062
063    public static final String ORIGINAL_VIEW_TITLE = "Original";
064
065    public static final String FILE_CONTENT_PROPERTY = "file:content";
066
067    public static final int BATCH_SIZE = 50;
068
069    public static final String DISABLE_QUOTA_CHECK_LISTENER = "disableQuotaListener";
070
071    @Override
072    public void doInitializeRepository(CoreSession session) {
073        if (Framework.getService(ConfigurationService.class).isBooleanTrue("nuxeo.picture.migration.enabled")) {
074            if (log.isInfoEnabled()) {
075                log.info(
076                        "Starting picture migration handler (this may take some time depending on the number of documents)");
077            }
078            boolean txStarted = false;
079            if (!TransactionHelper.isTransactionActive()) {
080                txStarted = true;
081            }
082
083            try {
084                doMigration(session);
085            } finally {
086                if (txStarted) {
087                    TransactionHelper.commitOrRollbackTransaction();
088                }
089            }
090        }
091    }
092
093    protected void doMigration(CoreSession session) {
094        Set<String> pictureIds = getPictureIdsToMigrate(session);
095        if (pictureIds.isEmpty()) {
096            return;
097        }
098
099        if (log.isInfoEnabled()) {
100            log.info(String.format("Started migration of %d documents with the 'Picture' facet", pictureIds.size()));
101        }
102
103        long pictureMigratedCount = 0;
104        try {
105            for (String pictureId : pictureIds) {
106                if (migratePicture(session, pictureId)) {
107                    if (++pictureMigratedCount % BATCH_SIZE == 0) {
108                        TransactionHelper.commitOrRollbackTransaction();
109                        TransactionHelper.startTransaction();
110                    }
111                }
112            }
113        } finally {
114            TransactionHelper.commitOrRollbackTransaction();
115            TransactionHelper.startTransaction();
116        }
117
118        if (log.isInfoEnabled()) {
119            log.info(String.format("Finished migration of %d/%d documents with the 'Picture' facet",
120                    pictureMigratedCount, pictureIds.size()));
121        }
122    }
123
124    protected Set<String> getPictureIdsToMigrate(CoreSession session) {
125        IterableQueryResult it = null;
126        Set<String> pictureIds = new HashSet<>();
127
128        try {
129            it = session.queryAndFetch(PICTURES_TO_MIGRATE_QUERY, NXQL);
130
131            for (Map<String, Serializable> map : it) {
132                String id = (String) map.get(ECM_UUID);
133                if (id != null) {
134                    pictureIds.add(id);
135                }
136            }
137        } finally {
138            if (it != null) {
139                it.close();
140            }
141        }
142        return pictureIds;
143    }
144
145    protected boolean migratePicture(CoreSession session, String docId) {
146        DocumentModel picture = session.getDocument(new IdRef(docId));
147
148        if (log.isDebugEnabled()) {
149            log.debug(String.format("Migrating %s", picture));
150        }
151
152        MultiviewPicture multiviewPicture = picture.getAdapter(MultiviewPicture.class);
153        PictureView originalView = multiviewPicture.getView(ORIGINAL_VIEW_TITLE);
154        Blob blob = originalView.getBlob();
155        if (blob == null) {
156            if (log.isWarnEnabled()) {
157                log.warn(String.format("No Original view Blob found for %s", picture));
158            }
159            return false;
160        }
161        String filename = blob.getFilename();
162        filename = StringUtils.defaultString(filename).replaceAll("^Original_", "");
163        blob.setFilename(filename);
164        picture.setPropertyValue(FILE_CONTENT_PROPERTY, (Serializable) blob);
165        multiviewPicture.removeView(ORIGINAL_VIEW_TITLE);
166        if (picture.isVersion()) {
167            picture.putContextData(ALLOW_VERSION_WRITE, Boolean.TRUE);
168        }
169        // disable quota if installed
170        picture.putContextData(DISABLE_QUOTA_CHECK_LISTENER, Boolean.TRUE);
171        session.saveDocument(picture);
172        return true;
173    }
174
175}