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