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