001/*
002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     Anahide Tchertchian
011 */
012package org.nuxeo.ecm.core.api.model.impl.primitives;
013
014import java.io.IOException;
015import java.io.Serializable;
016import java.util.HashMap;
017import java.util.Map;
018
019import org.nuxeo.ecm.core.api.Blob;
020import org.nuxeo.ecm.core.api.NuxeoException;
021import org.nuxeo.ecm.core.api.PropertyException;
022import org.nuxeo.ecm.core.api.blobholder.BlobHolderAdapterService;
023import org.nuxeo.ecm.core.api.model.InvalidPropertyValueException;
024import org.nuxeo.ecm.core.api.model.Property;
025import org.nuxeo.ecm.core.api.model.PropertyConversionException;
026import org.nuxeo.ecm.core.api.model.ReadOnlyPropertyException;
027import org.nuxeo.ecm.core.api.model.impl.MapProperty;
028import org.nuxeo.ecm.core.schema.types.Field;
029import org.nuxeo.runtime.api.Framework;
030
031/**
032 * Property handling an external blob: create/edit is done from a map, and the value returned is a blob.
033 * <p>
034 * Create/edit from a blob is not handled, and the blob uri cannot be retrieved from the blob (no api for now).
035 *
036 * @author Anahide Tchertchian
037 */
038public class ExternalBlobProperty extends MapProperty {
039
040    private static final long serialVersionUID = 1L;
041
042    // constants based on core-types.xsd fields. XXX Should be in model
043    public static final String ENCODING = "encoding";
044
045    public static final String MIME_TYPE = "mime-type";
046
047    public static final String FILE_NAME = "name";
048
049    public static final String DIGEST = "digest";
050
051    public static final String LENGTH = "length";
052
053    public static final String URI = "uri";
054
055    public ExternalBlobProperty(Property parent, Field field, int flags) {
056        super(parent, field, flags);
057    }
058
059    public ExternalBlobProperty(Property parent, Field field) {
060        super(parent, field);
061    }
062
063    @Override
064    @SuppressWarnings("unchecked")
065    public void init(Serializable value) throws PropertyException {
066        if (value == null) {
067            // IGNORE null values - properties will be
068            // considered PHANTOMS
069            return;
070        }
071        Map<String, Serializable> map;
072        if (value instanceof Map) {
073            map = (Map<String, Serializable>) value;
074        } else if (value instanceof Blob) {
075            // XXX: workaround: get the uri from the local prop because it's not on
076            // the Blob
077            map = getMapFromBlobWithUri((Blob) value);
078        } else {
079            throw new PropertyException("Invalid value for external blob (map or blob needed): " + value);
080        }
081        for (Entry<String, Serializable> entry : map.entrySet()) {
082            Property property = get(entry.getKey());
083            property.init(entry.getValue());
084        }
085        removePhantomFlag();
086    }
087
088    @Override
089    @SuppressWarnings("unchecked")
090    public Serializable internalGetValue() throws PropertyException {
091        Object mapValue = super.internalGetValue();
092        if (mapValue instanceof Map) {
093            Blob blob = getBlobFromMap((Map) mapValue);
094            if (blob != null && !(blob instanceof Serializable)) {
095                throw new PropertyException("Blob is not serializable: " + blob);
096            }
097            return (Serializable) blob;
098        } else if (mapValue != null) {
099            throw new PropertyException("Invalid value for external blob (map needed): " + mapValue);
100        }
101        return null;
102    }
103
104    @Override
105    @SuppressWarnings("unchecked")
106    public <T> T getValue(Class<T> type) throws PropertyException {
107        Object value = super.internalGetValue();
108        if (value == null) {
109            return null;
110        }
111        if (value instanceof Map) {
112            if (Map.class.isAssignableFrom(type)) {
113                return (T) value;
114            }
115            if (Blob.class.isAssignableFrom(type)) {
116                return (T) getBlobFromMap((Map) value);
117            }
118        }
119        throw new PropertyConversionException(value.getClass(), type);
120    }
121
122    @Override
123    public Serializable getValueForWrite() throws PropertyException {
124        return (Serializable) getValue(Map.class);
125    }
126
127    /**
128     * Overridden to be able to set a blob from a given map.
129     * <p>
130     * Take care of not overriding the uri if set as this information is not on the blob.
131     *
132     * @throws PropertyException if one of the sub properties throws an exception or if trying to set values to a blob
133     *             without any already existing uri set.
134     */
135    @Override
136    @SuppressWarnings("unchecked")
137    public void setValue(Object value) throws PropertyException {
138        if (!isContainer()) { // if not a container use default setValue()
139            super.setValue(value);
140            return;
141        }
142        if (isReadOnly()) {
143            throw new ReadOnlyPropertyException(getPath());
144        }
145        if (value == null) {
146            remove();
147            return; // TODO how to treat nulls?
148        }
149        if (value instanceof Blob) {
150            Property property = get(URI);
151            Object uri = property.getValue();
152            if (uri == null) {
153                throw new PropertyException("Cannot set blob properties without " + "an existing uri set");
154            }
155            // only update additional properties
156            Map<String, Serializable> map = getMapFromBlob((Blob) value);
157            for (Entry<String, Serializable> entry : map.entrySet()) {
158                String entryKey = entry.getKey();
159                if (entryKey != URI) {
160                    property = get(entryKey);
161                    property.setValue(entry.getValue());
162                }
163            }
164            return;
165        }
166        if (!(value instanceof Map)) {
167            throw new InvalidPropertyValueException(getPath());
168        }
169        Map<String, Object> map = (Map<String, Object>) value;
170        for (Entry<String, Object> entry : map.entrySet()) {
171            Property property = get(entry.getKey());
172            property.setValue(entry.getValue());
173        }
174    }
175
176    @Override
177    protected boolean isSameValue(Serializable value1, Serializable value2) {
178        // for now, blob property are considered always as dirty when update - see NXP-16322
179        return false;
180    }
181
182    public static Blob getBlobFromMap(Map<String, Object> mapValue) {
183        if (mapValue == null) {
184            return null;
185        }
186        String uri = (String) mapValue.get(URI);
187        if (uri == null || "".equals(uri)) {
188            return null;
189        }
190        String filename = (String) mapValue.get(FILE_NAME);
191        String mimeType = (String) mapValue.get(MIME_TYPE);
192        String encoding = (String) mapValue.get(ENCODING);
193        String digest = (String) mapValue.get(DIGEST);
194        try {
195            BlobHolderAdapterService service = Framework.getService(BlobHolderAdapterService.class);
196            if (service == null) {
197                throw new NuxeoException("BlobHolderAdapterService not found");
198            }
199            Blob blob = service.getExternalBlobForUri(uri);
200            if (filename != null) {
201                blob.setFilename(filename);
202            }
203            blob.setMimeType(mimeType);
204            blob.setEncoding(encoding);
205            // TODO maybe check if digest is still a match to the retrieved blob
206            blob.setDigest(digest);
207            return blob;
208        } catch (IOException e) {
209            throw new NuxeoException(e);
210        }
211    }
212
213    public Map<String, Serializable> getMapFromBlobWithUri(Blob blob) throws PropertyException {
214        Map<String, Serializable> map = getMapFromBlob(blob);
215        Property property = get(URI);
216        Serializable uri = property.getValue();
217        map.put(URI, uri);
218        return map;
219    }
220
221    public static Map<String, Serializable> getMapFromBlob(Blob blob) {
222        Map<String, Serializable> map = new HashMap<String, Serializable>();
223        if (blob == null) {
224            map.put(FILE_NAME, null);
225            map.put(MIME_TYPE, null);
226            map.put(ENCODING, null);
227            map.put(LENGTH, null);
228            map.put(DIGEST, null);
229        } else {
230            // cannot return uri for blob for now: no edit implemented
231            map.put(FILE_NAME, blob.getFilename());
232            map.put(MIME_TYPE, blob.getMimeType());
233            map.put(ENCODING, blob.getEncoding());
234            map.put(LENGTH, blob.getLength());
235            map.put(DIGEST, blob.getDigest());
236        }
237        return map;
238    }
239
240}