001/*
002 * (C) Copyright 2006-2019 Nuxeo (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 *     Bogdan Stefanescu
018 *     Florent Guillaume
019 */
020package org.nuxeo.ecm.core.api.model.impl.primitives;
021
022import java.io.ByteArrayInputStream;
023import java.io.Serializable;
024import java.util.Map;
025import java.util.Objects;
026
027import org.nuxeo.ecm.core.api.Blob;
028import org.nuxeo.ecm.core.api.NuxeoException;
029import org.nuxeo.ecm.core.api.PropertyException;
030import org.nuxeo.ecm.core.api.model.Property;
031import org.nuxeo.ecm.core.api.model.PropertyConversionException;
032import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
033import org.nuxeo.ecm.core.api.model.impl.MapProperty;
034import org.nuxeo.ecm.core.api.model.impl.ScalarProperty;
035import org.nuxeo.ecm.core.blob.ManagedBlob;
036import org.nuxeo.ecm.core.schema.types.Field;
037
038/**
039 * Blob property, reading and writing from a {@link Blob} object.
040 */
041public class BlobProperty extends MapProperty {
042
043    private static final long serialVersionUID = 1L;
044
045    private static final String NAME = "name";
046
047    private static final String MIME_TYPE = "mime-type";
048
049    private static final String ENCODING = "encoding";
050
051    private static final String DIGEST = "digest";
052
053    private static final String LENGTH = "length";
054
055    protected Serializable value;
056
057    public BlobProperty(Property parent, Field field, int flags) {
058        super(parent, field, flags);
059    }
060
061    @Override
062    public Serializable getDefaultValue() {
063        return null;
064    }
065
066    @Override
067    public Serializable internalGetValue() throws PropertyException {
068        return value;
069    }
070
071    @Override
072    public void internalSetValue(Serializable value) throws PropertyException {
073        this.value = value;
074    }
075
076    @Override
077    public boolean isNormalized(Object value) {
078        return value == null || ((value instanceof Blob) && (value instanceof Serializable));
079    }
080
081    @Override
082    public Serializable normalize(Object value) throws PropertyConversionException {
083        if (isNormalized(value)) {
084            // TODO specific blob support?
085            return (Serializable) value;
086        }
087        throw new PropertyConversionException(value.getClass(), Blob.class);
088    }
089
090    @SuppressWarnings("unchecked")
091    @Override
092    public <T> T convertTo(Serializable value, Class<T> toType) throws PropertyConversionException {
093        if (value == null) {
094            return null;
095        }
096        if (Blob.class.isAssignableFrom(toType)) {
097            return (T) value;
098        }
099        throw new PropertyConversionException(value.getClass(), toType);
100    }
101
102    @Override
103    public Object newInstance() {
104        return new ByteArrayInputStream("".getBytes()); // TODO not serializable
105    }
106
107    @Override
108    public boolean isContainer() {
109        return false;
110    }
111
112    @SuppressWarnings("unchecked")
113    @Override
114    public void setValue(Object value) throws PropertyException {
115        if (value instanceof Map) {
116            setMap(getValue(), (Map<String, Object>) value);
117            setIsModified();
118        } else {
119            super.setValue(value);
120        }
121    }
122
123    @Override
124    protected boolean isSameValue(Serializable value1, Serializable value2) {
125        if (value1 == null && value2 == null) {
126            // don't make property dirty if previous and current are null
127            return true;
128        } else if (value1 == value2) {
129            // if previous is the same object than current, it means that we first get the blob, edit it and then set it
130            return false;
131        } else if (value1 instanceof ManagedBlob && value2 instanceof ManagedBlob) {
132            return isSameValue((ManagedBlob) value1, (ManagedBlob) value2);
133        }
134        // for now and remaining cases, blob property are considered as dirty when update - see NXP-16322
135        return false;
136    }
137
138    protected boolean isSameValue(ManagedBlob value1, ManagedBlob value2) {
139        return Objects.equals(value1.getKey(), value2.getKey())
140                // we need to check other fields for blob dispatchers
141                && isNullOrSameValue(value1.getMimeType(), value2.getMimeType())
142                && isNullOrSameValue(value1.getEncoding(), value2.getEncoding())
143                && isNullOrSameValue(value1.getFilename(), value2.getFilename())
144                && isNullOrSameValue(value1.getDigest(), value2.getDigest());
145    }
146
147    protected boolean isNullOrSameValue(Serializable value1, Serializable value2) {
148        return value1 == null || value2 == null || value1.equals(value2);
149    }
150
151    @Override
152    public void init(Serializable value) throws PropertyException {
153        if (value == null) {
154            // IGNORE null values - properties will be
155            // considered PHANTOMS
156            return;
157        }
158        if (value instanceof Blob) {
159            internalSetValue(value);
160        }
161        removePhantomFlag();
162    }
163
164    @Override
165    public Serializable getValueForWrite() throws PropertyException {
166        return getValue();
167    }
168
169    @Override
170    protected Property internalGetChild(Field field) {
171        return new ScalarMemberProperty(this, field, isPhantom() ? IS_PHANTOM : 0);
172    }
173
174    protected void setMap(Object object, Map<String, Object> value) throws PropertyException {
175        if (object == null) {
176            throw new NuxeoException("Trying to access a member of a null object");
177        }
178        if (!(object instanceof Blob)) {
179            throw new NuxeoException("Not a Blob: " + object);
180        }
181        Blob blob = (Blob) object;
182        for (Entry<String, Object> entry : value.entrySet()) {
183            String name = entry.getKey();
184            Object v = entry.getValue();
185            setMemberValue(blob, name, v);
186        }
187    }
188
189    protected void setMemberValue(Blob blob, String name, Object value) throws PropertyNotFoundException {
190        if (NAME.equals(name)) {
191            blob.setFilename((String) value);
192        } else if (MIME_TYPE.equals(name)) {
193            blob.setMimeType((String) value);
194        } else if (ENCODING.equals(name)) {
195            blob.setEncoding((String) value);
196        } else if (DIGEST.equals(name)) {
197            blob.setDigest((String) value);
198        } else {
199            throw new PropertyNotFoundException(name);
200        }
201    }
202
203    protected Object getMemberValue(Object object, String name) throws PropertyException {
204        if (object == null) {
205            throw new NuxeoException("Trying to access a member of a null object: " + name);
206        }
207        if (!(object instanceof Blob)) {
208            throw new NuxeoException("Not a Blob: " + object);
209        }
210        Blob blob = (Blob) object;
211        if (NAME.equals(name)) {
212            return blob.getFilename();
213        } else if (MIME_TYPE.equals(name)) {
214            return blob.getMimeType();
215        } else if (ENCODING.equals(name)) {
216            return blob.getEncoding();
217        } else if (DIGEST.equals(name)) {
218            return blob.getDigest();
219        } else if (LENGTH.equals(name)) {
220            return Long.valueOf(blob.getLength());
221        } else {
222            throw new PropertyNotFoundException(name);
223        }
224    }
225
226    protected void setMemberValue(Object object, String name, Object value) throws PropertyException {
227        if (object == null) {
228            throw new NuxeoException("Trying to access a member of a null object: " + name);
229        }
230        if (!(object instanceof Blob)) {
231            throw new NuxeoException("Not a Blob: " + object);
232        }
233        Blob blob = (Blob) object;
234        setMemberValue(blob, name, value);
235    }
236
237    public static class ScalarMemberProperty extends ScalarProperty {
238
239        private static final long serialVersionUID = 1L;
240
241        public ScalarMemberProperty(Property parent, Field field, int flags) {
242            super(parent, field, flags);
243        }
244
245        @Override
246        public void internalSetValue(Serializable value) throws PropertyException {
247            ((BlobProperty) parent).setMemberValue(parent.getValue(), getName(), value);
248        }
249
250        @Override
251        public Serializable internalGetValue() throws PropertyException {
252            Object value = ((BlobProperty) parent).getMemberValue(parent.getValue(), getName());
253            if (value != null && !(value instanceof Serializable)) {
254                throw new PropertyException("Non serializable value: " + value);
255            }
256            return (Serializable) value;
257        }
258    }
259
260    @Override
261    public boolean isSameAs(Property property) throws PropertyException {
262        if (!(property instanceof BlobProperty)) {
263            return false;
264        }
265        BlobProperty other = (BlobProperty) property;
266        return Objects.equals(getValue(), other.getValue());
267    }
268
269}