001/*
002 * (C) Copyright 2015 Nuxeo SA (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.lang.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 String FILE_FILENAME_PROPERTY = "file:filename";
068
069    public static final int BATCH_SIZE = 50;
070
071    public static final String DISABLE_QUOTA_CHECK_LISTENER = "disableQuotaListener";
072
073    @Override
074    public void doInitializeRepository(CoreSession session) {
075        if (Framework.getService(ConfigurationService.class).isBooleanPropertyTrue("nuxeo.picture.migration.enabled")) {
076            if (log.isInfoEnabled()) {
077                log.info(
078                        "Starting picture migration handler (this may take some time depending on the number of documents)");
079            }
080            boolean txStarted = false;
081            if (!TransactionHelper.isTransactionActive()) {
082                txStarted = true;
083            }
084
085            try {
086                doMigration(session);
087            } finally {
088                if (txStarted) {
089                    TransactionHelper.commitOrRollbackTransaction();
090                }
091            }
092        }
093    }
094
095    protected void doMigration(CoreSession session) {
096        Set<String> pictureIds = getPictureIdsToMigrate(session);
097        if (pictureIds.isEmpty()) {
098            return;
099        }
100
101        if (log.isInfoEnabled()) {
102            log.info(String.format("Started migration of %d documents with the 'Picture' facet", pictureIds.size()));
103        }
104
105        long pictureMigratedCount = 0;
106        try {
107            for (String pictureId : pictureIds) {
108                if (migratePicture(session, pictureId)) {
109                    if (++pictureMigratedCount % BATCH_SIZE == 0) {
110                        TransactionHelper.commitOrRollbackTransaction();
111                        TransactionHelper.startTransaction();
112                    }
113                }
114            }
115        } finally {
116            TransactionHelper.commitOrRollbackTransaction();
117            TransactionHelper.startTransaction();
118        }
119
120        if (log.isInfoEnabled()) {
121            log.info(String.format("Finished migration of %d/%d documents with the 'Picture' facet",
122                    pictureMigratedCount, pictureIds.size()));
123        }
124    }
125
126    protected Set<String> getPictureIdsToMigrate(CoreSession session) {
127        IterableQueryResult it = null;
128        Set<String> pictureIds = new HashSet<>();
129
130        try {
131            it = session.queryAndFetch(PICTURES_TO_MIGRATE_QUERY, NXQL);
132
133            for (Map<String, Serializable> map : it) {
134                String id = (String) map.get(ECM_UUID);
135                if (id != null) {
136                    pictureIds.add(id);
137                }
138            }
139        } finally {
140            if (it != null) {
141                it.close();
142            }
143        }
144        return pictureIds;
145    }
146
147    protected boolean migratePicture(CoreSession session, String docId) {
148        DocumentModel picture = session.getDocument(new IdRef(docId));
149
150        if (log.isDebugEnabled()) {
151            log.debug(String.format("Migrating %s", picture));
152        }
153
154        MultiviewPicture multiviewPicture = picture.getAdapter(MultiviewPicture.class);
155        PictureView originalView = multiviewPicture.getView(ORIGINAL_VIEW_TITLE);
156        Blob blob = originalView.getBlob();
157        if (blob == null) {
158            if (log.isWarnEnabled()) {
159                log.warn(String.format("No Original view Blob found for %s", picture));
160            }
161            return false;
162        }
163        String filename = blob.getFilename();
164        filename = StringUtils.defaultString(filename).replaceAll("^Original_", "");
165        blob.setFilename(filename);
166        picture.setPropertyValue(FILE_CONTENT_PROPERTY, (Serializable) blob);
167        picture.setPropertyValue(FILE_FILENAME_PROPERTY, filename);
168        multiviewPicture.removeView(ORIGINAL_VIEW_TITLE);
169        if (picture.isVersion()) {
170            picture.putContextData(ALLOW_VERSION_WRITE, Boolean.TRUE);
171        }
172        // disable quota if installed
173        picture.putContextData(DISABLE_QUOTA_CHECK_LISTENER, Boolean.TRUE);
174        session.saveDocument(picture);
175        return true;
176    }
177
178}