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}