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