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) 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) 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()) { 150 throw new ReadOnlyPropertyException(getPath()); 151 } 152 if (value == null) { 153 remove(); 154 return; // TODO how to treat nulls? 155 } 156 if (value instanceof Blob) { 157 Property property = get(URI); 158 Object uri = property.getValue(); 159 if (uri == null) { 160 throw new PropertyException("Cannot set blob properties without " + "an existing uri set"); 161 } 162 // only update additional properties 163 Map<String, Serializable> map = getMapFromBlob((Blob) value); 164 for (Entry<String, Serializable> entry : map.entrySet()) { 165 String entryKey = entry.getKey(); 166 if (entryKey != URI) { 167 property = get(entryKey); 168 property.setValue(entry.getValue()); 169 } 170 } 171 return; 172 } 173 if (!(value instanceof Map)) { 174 throw new InvalidPropertyValueException(getPath()); 175 } 176 Map<String, Object> map = (Map<String, Object>) value; 177 for (Entry<String, Object> entry : map.entrySet()) { 178 Property property = get(entry.getKey()); 179 property.setValue(entry.getValue()); 180 } 181 } 182 183 @Override 184 protected boolean isSameValue(Serializable value1, Serializable value2) { 185 // for now, blob property are considered always as dirty when update - see NXP-16322 186 return false; 187 } 188 189 public static Blob getBlobFromMap(Map<String, Object> mapValue) { 190 if (mapValue == null) { 191 return null; 192 } 193 String uri = (String) mapValue.get(URI); 194 if (uri == null || "".equals(uri)) { 195 return null; 196 } 197 String filename = (String) mapValue.get(FILE_NAME); 198 String mimeType = (String) mapValue.get(MIME_TYPE); 199 String encoding = (String) mapValue.get(ENCODING); 200 String digest = (String) mapValue.get(DIGEST); 201 try { 202 BlobHolderAdapterService service = Framework.getService(BlobHolderAdapterService.class); 203 if (service == null) { 204 throw new NuxeoException("BlobHolderAdapterService not found"); 205 } 206 Blob blob = service.getExternalBlobForUri(uri); 207 if (filename != null) { 208 blob.setFilename(filename); 209 } 210 blob.setMimeType(mimeType); 211 blob.setEncoding(encoding); 212 // TODO maybe check if digest is still a match to the retrieved blob 213 blob.setDigest(digest); 214 return blob; 215 } catch (IOException e) { 216 throw new NuxeoException(e); 217 } 218 } 219 220 public Map<String, Serializable> getMapFromBlobWithUri(Blob blob) throws PropertyException { 221 Map<String, Serializable> map = getMapFromBlob(blob); 222 Property property = get(URI); 223 Serializable uri = property.getValue(); 224 map.put(URI, uri); 225 return map; 226 } 227 228 public static Map<String, Serializable> getMapFromBlob(Blob blob) { 229 Map<String, Serializable> map = new HashMap<String, Serializable>(); 230 if (blob == null) { 231 map.put(FILE_NAME, null); 232 map.put(MIME_TYPE, null); 233 map.put(ENCODING, null); 234 map.put(LENGTH, null); 235 map.put(DIGEST, null); 236 } else { 237 // cannot return uri for blob for now: no edit implemented 238 map.put(FILE_NAME, blob.getFilename()); 239 map.put(MIME_TYPE, blob.getMimeType()); 240 map.put(ENCODING, blob.getEncoding()); 241 map.put(LENGTH, blob.getLength()); 242 map.put(DIGEST, blob.getDigest()); 243 } 244 return map; 245 } 246 247}