001/*
002 * (C) Copyright 2010 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 * Contributors:
016 * Nuxeo - initial API and implementation
017 */
018package org.nuxeo.ecm.platform.rendition.service;
019
020import static org.nuxeo.ecm.platform.rendition.Constants.FILES_FILES_PROPERTY;
021import static org.nuxeo.ecm.platform.rendition.Constants.FILES_SCHEMA;
022import static org.nuxeo.ecm.platform.rendition.Constants.RENDITION_FACET;
023import static org.nuxeo.ecm.platform.rendition.Constants.RENDITION_NAME_PROPERTY;
024import static org.nuxeo.ecm.platform.rendition.Constants.RENDITION_SOURCE_ID_PROPERTY;
025import static org.nuxeo.ecm.platform.rendition.Constants.RENDITION_SOURCE_MODIFICATION_DATE_PROPERTY;
026import static org.nuxeo.ecm.platform.rendition.Constants.RENDITION_SOURCE_VERSIONABLE_ID_PROPERTY;
027import static org.nuxeo.ecm.platform.rendition.Constants.RENDITION_VARIANT_PROPERTY;
028
029import java.io.Serializable;
030import java.util.ArrayList;
031import java.util.Calendar;
032import java.util.Map;
033
034import org.apache.commons.logging.Log;
035import org.apache.commons.logging.LogFactory;
036import org.nuxeo.ecm.core.api.Blob;
037import org.nuxeo.ecm.core.api.DocumentModel;
038import org.nuxeo.ecm.core.api.DocumentModelList;
039import org.nuxeo.ecm.core.api.DocumentRef;
040import org.nuxeo.ecm.core.api.IdRef;
041import org.nuxeo.ecm.core.api.LifeCycleConstants;
042import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner;
043import org.nuxeo.ecm.core.api.VersioningOption;
044import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
045import org.nuxeo.ecm.core.api.blobholder.DocumentStringBlobHolder;
046import org.nuxeo.ecm.core.query.sql.NXQL;
047import org.nuxeo.ecm.core.versioning.VersioningService;
048import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeEntry;
049import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry;
050import org.nuxeo.runtime.api.Framework;
051
052/**
053 * @author <a href="mailto:troger@nuxeo.com">Thomas Roger</a>
054 */
055public class RenditionCreator extends UnrestrictedSessionRunner {
056
057    private static final Log log = LogFactory.getLog(RenditionCreator.class);
058
059    public static final String FILE = "File";
060
061    protected DocumentModel detachedRendition;
062
063    protected String liveDocumentId;
064
065    protected String versionDocumentId;
066
067    protected Blob renditionBlob;
068
069    protected String renditionName;
070
071    /**
072     * @since 8.1
073     */
074    protected RenditionDefinition renditionDefinition;
075
076    /**
077     * @since 8.1
078     */
079    protected final String renditionVariant;
080
081    /**
082     * @since 8.1
083     */
084    public RenditionCreator(DocumentModel liveDocument, DocumentModel versionDocument, Blob renditionBlob,
085            RenditionDefinition renditionDefinition) {
086        super(liveDocument.getCoreSession());
087        liveDocumentId = liveDocument.getId();
088        versionDocumentId = versionDocument == null ? null : versionDocument.getId();
089        this.renditionBlob = renditionBlob;
090        this.renditionDefinition = renditionDefinition;
091        renditionName = renditionDefinition.getName();
092        renditionVariant = renditionDefinition.getProvider().getVariant(liveDocument, renditionDefinition);
093    }
094
095    public DocumentModel getDetachedRendition() {
096        return detachedRendition;
097    }
098
099    /**
100     * @deprecated since 7.10, misspelled, use {@link #getDetachedRendition} instead.
101     */
102    @Deprecated
103    public DocumentModel getDetachedDendition() {
104        return detachedRendition;
105    }
106
107    @Override
108    public void run() {
109        DocumentModel liveDocument = session.getDocument(new IdRef(liveDocumentId));
110        DocumentModel sourceDocument = liveDocument.isVersionable() ? session.getDocument(new IdRef(versionDocumentId))
111                : liveDocument;
112        DocumentModel rendition = createRenditionDocument(sourceDocument);
113        removeBlobs(rendition);
114        updateMainBlob(rendition);
115        updateIconAndSizeFields(rendition);
116
117        // create a copy of the doc
118        if (rendition.getId() == null) {
119            rendition = session.createDocument(rendition);
120        }
121        if (sourceDocument.isVersionable()) {
122            // be sure to have the same version info
123            setCorrectVersion(rendition, sourceDocument);
124        }
125        // do not apply default versioning to rendition
126        rendition.putContextData(VersioningService.VERSIONING_OPTION, VersioningOption.NONE);
127        rendition = session.saveDocument(rendition);
128
129        if (sourceDocument.isVersionable()) {
130            // rendition is checked out: check it in
131            DocumentRef renditionRef = rendition.checkIn(VersioningOption.NONE, null);
132            rendition = session.getDocument(renditionRef);
133        }
134        session.save();
135
136        rendition.detach(true);
137        detachedRendition = rendition;
138    }
139
140    protected DocumentModel createRenditionDocument(DocumentModel sourceDocument) {
141        String doctype = sourceDocument.getType();
142        String renditionMimeType = renditionBlob.getMimeType();
143        BlobHolder blobHolder = sourceDocument.getAdapter(BlobHolder.class);
144        if (blobHolder == null || (blobHolder instanceof DocumentStringBlobHolder
145                && !(renditionMimeType.startsWith("text/") || renditionMimeType.startsWith("application/xhtml")))) {
146            // We have a document type unable to hold blobs, or
147            // We have a Note or other blob holder that can only hold strings, but the rendition is not a string-related
148            // MIME type.
149            // In either case, we'll have to create a File to hold it.
150            doctype = FILE;
151        }
152
153        boolean isVersionable = sourceDocument.isVersionable();
154        String liveDocProp = isVersionable ? RENDITION_SOURCE_VERSIONABLE_ID_PROPERTY : RENDITION_SOURCE_ID_PROPERTY;
155        StringBuilder query = new StringBuilder();
156        query.append("SELECT * FROM Document WHERE ecm:isProxy = 0 AND ");
157        query.append(RENDITION_NAME_PROPERTY);
158        query.append(" = '");
159        query.append(NXQL.escapeStringInner(renditionName));
160        query.append("' AND ");
161        if (renditionVariant != null) {
162            query.append(RENDITION_VARIANT_PROPERTY);
163            query.append(" = '");
164            query.append(NXQL.escapeStringInner(renditionVariant));
165            query.append("' AND ");
166        }
167        query.append(liveDocProp);
168        query.append(" = '");
169        query.append(liveDocumentId);
170        query.append("'");
171        DocumentModelList existingRenditions = session.query(query.toString());
172        String modificationDatePropertyName = getSourceDocumentModificationDatePropertyName();
173        Calendar sourceLastModified = (Calendar) sourceDocument.getPropertyValue(modificationDatePropertyName);
174        DocumentModel rendition = null;
175        if (existingRenditions.size() > 0) {
176            rendition = session.getDocument(existingRenditions.get(0).getRef());
177            if (!isVersionable) {
178                Calendar renditionSourceLastModified = (Calendar) rendition.getPropertyValue(
179                        RENDITION_SOURCE_MODIFICATION_DATE_PROPERTY);
180                if (renditionSourceLastModified != null && !renditionSourceLastModified.before(sourceLastModified)) {
181                    this.renditionBlob = (Blob) rendition.getPropertyValue("file:content");
182                    return rendition;
183                }
184            }
185            if (rendition.isVersion()) {
186                String sid = rendition.getVersionSeriesId();
187                rendition = session.getDocument(new IdRef(sid));
188            }
189        } else {
190            rendition = session.createDocumentModel(null, sourceDocument.getName(), doctype);
191        }
192
193        rendition.copyContent(sourceDocument);
194        rendition.getContextData().putScopedValue(LifeCycleConstants.INITIAL_LIFECYCLE_STATE_OPTION_NAME,
195                sourceDocument.getCurrentLifeCycleState());
196
197        rendition.addFacet(RENDITION_FACET);
198        rendition.setPropertyValue(RENDITION_SOURCE_ID_PROPERTY, sourceDocument.getId());
199        if (isVersionable) {
200            rendition.setPropertyValue(RENDITION_SOURCE_VERSIONABLE_ID_PROPERTY, liveDocumentId);
201        }
202        if (sourceLastModified != null) {
203            rendition.setPropertyValue(RENDITION_SOURCE_MODIFICATION_DATE_PROPERTY, sourceLastModified);
204        }
205        if (renditionVariant != null) {
206            rendition.setPropertyValue(RENDITION_VARIANT_PROPERTY, renditionVariant);
207        }
208        rendition.setPropertyValue(RENDITION_NAME_PROPERTY, renditionName);
209
210        return rendition;
211    }
212
213    protected void removeBlobs(DocumentModel rendition) {
214        if (rendition.hasSchema(FILES_SCHEMA)) {
215            rendition.setPropertyValue(FILES_FILES_PROPERTY, new ArrayList<Map<String, Serializable>>());
216        }
217    }
218
219    protected void updateMainBlob(DocumentModel rendition) {
220        BlobHolder bh = rendition.getAdapter(BlobHolder.class);
221        bh.setBlob(renditionBlob);
222    }
223
224    private void updateIconAndSizeFields(DocumentModel rendition) {
225        if (!rendition.hasSchema("common")) {
226            return;
227        }
228        MimetypeRegistry mimetypeService;
229        try {
230            mimetypeService = Framework.getService(MimetypeRegistry.class);
231        } catch (Exception e) {
232            log.error("Cannot fetch Mimetype service when updating icon and file size rendition", e);
233            return;
234        }
235        MimetypeEntry mimetypeEntry = mimetypeService.getMimetypeEntryByMimeType(renditionBlob.getMimeType());
236        if (mimetypeEntry != null && mimetypeEntry.getIconPath() != null) {
237            rendition.setPropertyValue("common:icon", "/icons/" + mimetypeEntry.getIconPath());
238        }
239        rendition.setPropertyValue("common:size", renditionBlob.getLength());
240    }
241
242    protected void setCorrectVersion(DocumentModel rendition, DocumentModel versionDocument) {
243        Long minorVersion = (Long) versionDocument.getPropertyValue("uid:minor_version");
244        rendition.setPropertyValue("uid:minor_version", minorVersion);
245        rendition.setPropertyValue("uid:major_version", versionDocument.getPropertyValue("uid:major_version"));
246    }
247
248    protected String getSourceDocumentModificationDatePropertyName() {
249        return renditionDefinition.getSourceDocumentModificationDatePropertyName();
250    }
251
252}