001/*
002 * (C) Copyright 2006-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.core.api.blobholder;
020
021import java.io.Serializable;
022import java.util.ArrayList;
023import java.util.Calendar;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Map;
027
028import org.nuxeo.ecm.core.api.Blob;
029import org.nuxeo.ecm.core.api.DocumentModel;
030import org.nuxeo.ecm.core.api.model.Property;
031import org.nuxeo.ecm.core.utils.BlobsExtractor;
032
033/**
034 * {@link BlobHolder} implementation based on a {@link DocumentModel} and a XPath.
035 *
036 * @author tiry
037 */
038public class DocumentBlobHolder extends AbstractBlobHolder {
039
040    protected final DocumentModel doc;
041
042    protected final String xPath;
043
044    protected List<Blob> blobList = null;
045
046    public DocumentBlobHolder(DocumentModel doc, String xPath) {
047        if (xPath == null) {
048            throw new IllegalArgumentException("Invalid null xpath for document: " + doc);
049        }
050        this.doc = doc;
051        this.xPath = xPath;
052    }
053
054    protected DocumentBlobHolder(DocumentModel doc, String xPath, List<Blob> blobList) {
055        this(doc, xPath);
056        this.blobList = blobList;
057    }
058
059    @Override
060    protected String getBasePath() {
061        return doc.getPathAsString();
062    }
063
064    @Override
065    public Blob getBlob() {
066        return (Blob) doc.getPropertyValue(xPath);
067    }
068
069    @Override
070    public void setBlob(Blob blob) {
071        doc.getProperty(xPath).setValue(blob);
072    }
073
074    @Override
075    public Calendar getModificationDate() {
076        return (Calendar) doc.getProperty("dublincore", "modified");
077    }
078
079    @Override
080    public String getHash() {
081        Blob blob = getBlob();
082        if (blob != null) {
083            String h = blob.getDigest();
084            if (h != null) {
085                return h;
086            }
087        }
088        return doc.getId() + xPath + String.valueOf(getModificationDate());
089    }
090
091    @Override
092    public Serializable getProperty(String name) {
093        return null;
094    }
095
096    @Override
097    public Map<String, Serializable> getProperties() {
098        return null;
099    }
100
101    @Override
102    public List<Blob> getBlobs() {
103        if (blobList == null) {
104            computeBlobList();
105        }
106        return blobList;
107    }
108
109    /**
110     * Returns a new {@link DocumentBlobHolder} for the blob at the given {@code index} where {@link #getBlob} and
111     * {@link #getXpath} will return information about the blob.
112     *
113     * @param index the blob index
114     * @return the new blob holder
115     * @throws IndexOutOfBoundsException if the index is invalid
116     * @since 9.3
117     */
118    public DocumentBlobHolder asDirectBlobHolder(int index) throws IndexOutOfBoundsException {
119        List<Property> properties = computeBlobList();
120        // find real xpath for the property at that index in the list
121        String xpath = getFullXPath(properties.get(index));
122        // keep the same blobList, even though its first element doesn't correspond to the xpath anymore
123        return new DocumentBlobHolder(doc, xpath, blobList);
124    }
125
126    /**
127     * Computes the blob list, with the main blob first.
128     *
129     * @return the blob properties
130     * @since 9.3
131     */
132    protected List<Property> computeBlobList() {
133        List<Property> properties = new BlobsExtractor().getBlobsProperties(doc);
134        // be sure that the "main" blob is always in first position
135        Iterator<Property> it = properties.iterator();
136        boolean hasMainBlob = false;
137        while (it.hasNext()) {
138            Property property = it.next();
139            if (getFullXPath(property).equals(xPath)) {
140                it.remove();
141                properties.add(0, property);
142                hasMainBlob = true;
143                break;
144            }
145        }
146        if (!hasMainBlob) {
147            // the main blob may not be coming from a blob property in subclasses, find its property anyway
148            Property property = doc.getProperty(xPath);
149            properties.add(0, property);
150        }
151        blobList = new ArrayList<>(properties.size());
152        for (int i = 0; i < properties.size(); i++) {
153            if (i == 0) {
154                // the main blob may be computed differently in subclasses, always call getBlob()
155                Blob mainBlob = getBlob();
156                if (mainBlob != null) {
157                    blobList.add(mainBlob);
158                }
159            } else {
160                blobList.add((Blob) properties.get(i).getValue());
161            }
162        }
163        return properties;
164    }
165
166    /**
167     * Gets the full xpath for a property, including schema prefix in all cases.
168     *
169     * @since 9.3
170     */
171    protected String getFullXPath(Property property) {
172        String xpath = property.getXPath();
173        if (xpath.indexOf(':') < 0) {
174            // add schema name as prefix
175            xpath = property.getSchema().getName() + ':' + xpath;
176        }
177        return xpath;
178    }
179
180    /**
181     * @since 7.3
182     */
183    public String getXpath() {
184        return xPath;
185    }
186
187    /**
188     * @since 7.4
189     */
190    public DocumentModel getDocument() {
191        return doc;
192    }
193}