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}