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