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.transaction.TransactionHelper;
043
044/**
045 * Migrate old Original picture view to {@code file:content}.
046 * <p>
047 * Does not copy it if {@code file:content} is not empty. When done, the Original picture view is removed.
048 * <p>
049 * It does not recompute the picture views.
050 *
051 * @since 7.2
052 */
053public class PictureMigrationHandler extends RepositoryInitializationHandler {
054
055    private static final Log log = getLog(PictureMigrationHandler.class);
056
057    public static final String PICTURES_TO_MIGRATE_QUERY = "SELECT ecm:uuid FROM Document "
058            + "WHERE ecm:mixinType = 'Picture' AND ecm:isProxy = 0 AND views/*/title = 'Original' "
059            + "AND content/data IS NULL";
060
061    public static final String ORIGINAL_VIEW_TITLE = "Original";
062
063    public static final String FILE_CONTENT_PROPERTY = "file:content";
064
065    public static final String FILE_FILENAME_PROPERTY = "file:filename";
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        boolean txStarted = false;
074        if (!TransactionHelper.isTransactionActive()) {
075            txStarted = true;
076        }
077
078        try {
079            doMigration(session);
080        } finally {
081            if (txStarted) {
082                TransactionHelper.commitOrRollbackTransaction();
083            }
084        }
085    }
086
087    protected void doMigration(CoreSession session) {
088        Set<String> pictureIds = getPictureIdsToMigrate(session);
089        if (pictureIds.isEmpty()) {
090            return;
091        }
092
093        if (log.isWarnEnabled()) {
094            log.warn(String.format("Started migration of %d documents with the 'Picture' facet", pictureIds.size()));
095        }
096
097        long pictureMigratedCount = 0;
098        try {
099            for (String pictureId : pictureIds) {
100                if (migratePicture(session, pictureId)) {
101                    if (++pictureMigratedCount % BATCH_SIZE == 0) {
102                        TransactionHelper.commitOrRollbackTransaction();
103                        TransactionHelper.startTransaction();
104                    }
105                }
106            }
107        } finally {
108            TransactionHelper.commitOrRollbackTransaction();
109            TransactionHelper.startTransaction();
110        }
111
112        if (log.isWarnEnabled()) {
113            log.warn(String.format("Finished migration of %d/%d documents with the 'Picture' facet",
114                    pictureMigratedCount, pictureIds.size()));
115        }
116    }
117
118    protected Set<String> getPictureIdsToMigrate(CoreSession session) {
119        IterableQueryResult it = null;
120        Set<String> pictureIds = new HashSet<>();
121
122        try {
123            it = session.queryAndFetch(PICTURES_TO_MIGRATE_QUERY, NXQL);
124
125            for (Map<String, Serializable> map : it) {
126                String id = (String) map.get(ECM_UUID);
127                if (id != null) {
128                    pictureIds.add(id);
129                }
130            }
131        } finally {
132            if (it != null) {
133                it.close();
134            }
135        }
136        return pictureIds;
137    }
138
139    protected boolean migratePicture(CoreSession session, String docId) {
140        DocumentModel picture = session.getDocument(new IdRef(docId));
141
142        if (log.isDebugEnabled()) {
143            log.debug(String.format("Migrating %s", picture));
144        }
145
146        MultiviewPicture multiviewPicture = picture.getAdapter(MultiviewPicture.class);
147        PictureView originalView = multiviewPicture.getView(ORIGINAL_VIEW_TITLE);
148        Blob blob = originalView.getBlob();
149        if (blob == null) {
150            if (log.isWarnEnabled()) {
151                log.warn(String.format("No Original view Blob found for %s", picture));
152            }
153            return false;
154        }
155        String filename = blob.getFilename();
156        filename = StringUtils.defaultString(filename).replaceAll("^Original_", "");
157        blob.setFilename(filename);
158        picture.setPropertyValue(FILE_CONTENT_PROPERTY, (Serializable) blob);
159        picture.setPropertyValue(FILE_FILENAME_PROPERTY, filename);
160        multiviewPicture.removeView(ORIGINAL_VIEW_TITLE);
161        if (picture.isVersion()) {
162            picture.putContextData(ALLOW_VERSION_WRITE, Boolean.TRUE);
163        }
164        // disable quota if installed
165        picture.putContextData(DISABLE_QUOTA_CHECK_LISTENER, Boolean.TRUE);
166        session.saveDocument(picture);
167        return true;
168    }
169
170}