001/*
002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Thomas Roger
016 */
017
018package org.nuxeo.ecm.platform.picture;
019
020import static org.apache.commons.logging.LogFactory.getLog;
021import static org.nuxeo.ecm.core.api.CoreSession.ALLOW_VERSION_WRITE;
022import static org.nuxeo.ecm.core.query.sql.NXQL.ECM_UUID;
023import static org.nuxeo.ecm.core.query.sql.NXQL.NXQL;
024
025import java.io.Serializable;
026import java.util.HashSet;
027import java.util.Map;
028import java.util.Set;
029
030import org.apache.commons.logging.Log;
031import org.nuxeo.ecm.core.api.Blob;
032import org.nuxeo.ecm.core.api.CoreSession;
033import org.nuxeo.ecm.core.api.DocumentModel;
034import org.nuxeo.ecm.core.api.IdRef;
035import org.nuxeo.ecm.core.api.IterableQueryResult;
036import org.nuxeo.ecm.core.repository.RepositoryInitializationHandler;
037import org.nuxeo.ecm.platform.picture.api.PictureView;
038import org.nuxeo.ecm.platform.picture.api.adapters.MultiviewPicture;
039import org.nuxeo.runtime.transaction.TransactionHelper;
040
041/**
042 * Migrate old Original picture view to {@code file:content}.
043 * <p>
044 * Does not copy it if {@code file:content} is not empty. When done, the Original picture view is removed.
045 * <p>
046 * It does not recompute the picture views.
047 *
048 * @since 7.2
049 */
050public class PictureMigrationHandler extends RepositoryInitializationHandler {
051
052    private static final Log log = getLog(PictureMigrationHandler.class);
053
054    public static final String PICTURES_TO_MIGRATE_QUERY = "SELECT ecm:uuid FROM Document "
055            + "WHERE ecm:mixinType = 'Picture' AND ecm:isProxy = 0 AND views/*/title = 'Original' "
056            + "AND content/data IS NULL";
057
058    public static final String ORIGINAL_VIEW_TITLE = "Original";
059
060    public static final String FILE_CONTENT_PROPERTY = "file:content";
061
062    public static final String FILE_FILENAME_PROPERTY = "file:filename";
063
064    public static final int BATCH_SIZE = 50;
065
066    public static final String DISABLE_QUOTA_CHECK_LISTENER = "disableQuotaListener";
067
068    @Override
069    public void doInitializeRepository(CoreSession session) {
070        boolean txStarted = false;
071        if (!TransactionHelper.isTransactionActive()) {
072            txStarted = true;
073        }
074
075        try {
076            doMigration(session);
077        } finally {
078            if (txStarted) {
079                TransactionHelper.commitOrRollbackTransaction();
080            }
081        }
082    }
083
084    protected void doMigration(CoreSession session) {
085        Set<String> pictureIds = getPictureIdsToMigrate(session);
086        if (pictureIds.isEmpty()) {
087            return;
088        }
089
090        if (log.isWarnEnabled()) {
091            log.warn(String.format("Started migration of %d documents with the 'Picture' facet", pictureIds.size()));
092        }
093
094        long pictureMigratedCount = 0;
095        try {
096            for (String pictureId : pictureIds) {
097                if (migratePicture(session, pictureId)) {
098                    if (++pictureMigratedCount % BATCH_SIZE == 0) {
099                        TransactionHelper.commitOrRollbackTransaction();
100                        TransactionHelper.startTransaction();
101                    }
102                }
103            }
104        } finally {
105            TransactionHelper.commitOrRollbackTransaction();
106            TransactionHelper.startTransaction();
107        }
108
109        if (log.isWarnEnabled()) {
110            log.warn(String.format("Finished migration of %d/%d documents with the 'Picture' facet",
111                    pictureMigratedCount, pictureIds.size()));
112        }
113    }
114
115    protected Set<String> getPictureIdsToMigrate(CoreSession session) {
116        IterableQueryResult it = null;
117        Set<String> pictureIds = new HashSet<>();
118
119        try {
120            it = session.queryAndFetch(PICTURES_TO_MIGRATE_QUERY, NXQL);
121
122            for (Map<String, Serializable> map : it) {
123                String id = (String) map.get(ECM_UUID);
124                if (id != null) {
125                    pictureIds.add(id);
126                }
127            }
128        } finally {
129            if (it != null) {
130                it.close();
131            }
132        }
133        return pictureIds;
134    }
135
136    protected boolean migratePicture(CoreSession session, String docId) {
137        DocumentModel picture = session.getDocument(new IdRef(docId));
138
139        if (log.isDebugEnabled()) {
140            log.debug(String.format("Migrating %s", picture));
141        }
142
143        MultiviewPicture multiviewPicture = picture.getAdapter(MultiviewPicture.class);
144        PictureView originalView = multiviewPicture.getView(ORIGINAL_VIEW_TITLE);
145        Blob blob = originalView.getBlob();
146        if (blob == null) {
147            if (log.isWarnEnabled()) {
148                log.warn(String.format("No Original view Blob found for %s", picture));
149            }
150            return false;
151        }
152        blob.setFilename(blob.getFilename().replaceAll("^Original_", ""));
153        picture.setPropertyValue(FILE_CONTENT_PROPERTY, (Serializable) blob);
154        picture.setPropertyValue(FILE_FILENAME_PROPERTY, blob.getFilename());
155        multiviewPicture.removeView(ORIGINAL_VIEW_TITLE);
156        if (picture.isVersion()) {
157            picture.putContextData(ALLOW_VERSION_WRITE, Boolean.TRUE);
158        }
159        // disable quota if installed
160        picture.putContextData(DISABLE_QUOTA_CHECK_LISTENER, Boolean.TRUE);
161        session.saveDocument(picture);
162        return true;
163    }
164
165}