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}