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