001/* 002 * (C) Copyright 2006-2007 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 * Nuxeo - initial API and implementation 018 * 019 * $Id: DocumentModelResolver.java 23589 2007-08-08 16:50:40Z fguillaume $ 020 */ 021 022package org.nuxeo.ecm.platform.el; 023 024import java.io.Serializable; 025import java.util.List; 026import java.util.Map; 027import java.util.stream.Collectors; 028 029import javax.el.BeanELResolver; 030import javax.el.ELContext; 031import javax.el.PropertyNotFoundException; 032 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035import org.nuxeo.ecm.core.api.Blob; 036import org.nuxeo.ecm.core.api.DocumentModel; 037import org.nuxeo.ecm.core.api.PropertyException; 038import org.nuxeo.ecm.core.api.model.Property; 039import org.nuxeo.ecm.core.api.model.impl.ArrayProperty; 040import org.nuxeo.ecm.core.api.model.impl.ComplexProperty; 041import org.nuxeo.ecm.core.api.model.impl.ListProperty; 042 043/** 044 * Resolves expressions for the {@link DocumentModel} framework. 045 * <p> 046 * To specify a property on a document mode, the following syntax is available: 047 * <code>myDocumentModel.dublincore.title</code> where 'dublincore' is the schema name and 'title' is the field name. It 048 * can be used to get or set the document title: {@code <h:outputText value="# {currentDocument.dublincore.title}" />} 049 * or {@code <h:inputText value="# {currentDocument.dublincore.title}" />}. 050 * <p> 051 * Simple document properties are get/set directly: for instance, the above expression will return a String value on 052 * get, and set this String on the document for set. Complex properties (maps and lists) are get/set through the 053 * {@link Property} object controlling their value: on get, sub properties will be resolved at the next iteration, and 054 * on set, they will be set on the property instance so the document model is aware of the change. 055 * 056 * @author <a href="mailto:[email protected]">Razvan Caraghin</a> 057 * @author <a href="mailto:[email protected]">Anahide Tchertchian</a> 058 */ 059public class DocumentModelResolver extends BeanELResolver { 060 061 private static final Log log = LogFactory.getLog(DocumentModelResolver.class); 062 063 // XXX AT: see if getFeatureDescriptor needs to be overloaded to return 064 // datamodels descriptors. 065 066 @Override 067 public Class<?> getType(ELContext context, Object base, Object property) { 068 Class<?> type = null; 069 if (base instanceof DocumentModel) { 070 try { 071 type = super.getType(context, base, property); 072 } catch (PropertyNotFoundException e) { 073 type = DocumentPropertyContext.class; 074 context.setPropertyResolved(true); 075 } 076 } else if (base instanceof DocumentPropertyContext || base instanceof Property) { 077 type = Object.class; 078 if (base instanceof DocumentPropertyContext) { 079 DocumentPropertyContext ctx = (DocumentPropertyContext) base; 080 try { 081 Property docProperty = getDocumentProperty(ctx, property); 082 if (docProperty.isContainer()) { 083 Property subProperty = getDocumentProperty(docProperty, property); 084 if (subProperty.isList()) { 085 type = List.class; 086 } 087 } else if (docProperty instanceof ArrayProperty) { 088 type = List.class; 089 } 090 } catch (PropertyException pe) { 091 // avoid errors, return Object 092 log.warn(pe.toString()); 093 } 094 } else if (base instanceof Property) { 095 try { 096 Property docProperty = (Property) base; 097 Property subProperty = getDocumentProperty(docProperty, property); 098 if (subProperty.isList()) { 099 type = List.class; 100 } 101 } catch (PropertyException pe) { 102 try { 103 // try property getters to resolve 104 // doc.schema.field.type for instance 105 type = super.getType(context, base, property); 106 } catch (PropertyNotFoundException e) { 107 // avoid errors, log original error and return Object 108 log.warn(pe.toString()); 109 } 110 } 111 } 112 context.setPropertyResolved(true); 113 } else if (base instanceof Blob) { 114 type = super.getType(context, base, getBlobMapping(property)); 115 } 116 return type; 117 } 118 119 @Override 120 public Object getValue(ELContext context, Object base, Object property) { 121 Object value = null; 122 if (base instanceof DocumentModel) { 123 try { 124 // try document getters first to resolve doc.id for instance 125 value = super.getValue(context, base, property); 126 } catch (PropertyNotFoundException e) { 127 value = new DocumentPropertyContext((DocumentModel) base, (String) property); 128 context.setPropertyResolved(true); 129 } 130 } else if (base instanceof DocumentPropertyContext) { 131 try { 132 DocumentPropertyContext ctx = (DocumentPropertyContext) base; 133 Property docProperty = getDocumentProperty(ctx, property); 134 value = getDocumentPropertyValue(docProperty); 135 } catch (PropertyException pe) { 136 // avoid errors, return null 137 log.warn(pe.toString()); 138 } 139 context.setPropertyResolved(true); 140 } else if (base instanceof Property) { 141 try { 142 Property docProperty = (Property) base; 143 Property subProperty = getDocumentProperty(docProperty, property); 144 value = getDocumentPropertyValue(subProperty); 145 } catch (PropertyException pe) { 146 try { 147 // try property getters to resolve doc.schema.field.type 148 // for instance 149 value = super.getValue(context, base, property); 150 } catch (PropertyNotFoundException e) { 151 // avoid errors, log original error and return null 152 log.warn(pe.toString()); 153 } 154 } 155 context.setPropertyResolved(true); 156 } else if (base instanceof Blob) { 157 value = super.getValue(context, base, getBlobMapping(property)); 158 } 159 160 return value; 161 } 162 163 private static String getDocumentPropertyName(DocumentPropertyContext ctx, Object propertyValue) { 164 return ctx.schema + ":" + propertyValue; 165 } 166 167 private static Property getDocumentProperty(DocumentPropertyContext ctx, Object propertyValue) 168 throws PropertyException { 169 return ctx.doc.getProperty(getDocumentPropertyName(ctx, propertyValue)); 170 } 171 172 @SuppressWarnings("boxing") 173 private static Property getDocumentProperty(Property docProperty, Object propertyValue) throws PropertyException { 174 Property subProperty = null; 175 if ((docProperty instanceof ArrayProperty || docProperty instanceof ListProperty) 176 && propertyValue instanceof Long) { 177 subProperty = docProperty.get(((Long) propertyValue).intValue()); 178 } else if ((docProperty instanceof ArrayProperty || docProperty instanceof ListProperty) 179 && propertyValue instanceof Integer) { 180 Integer idx = (Integer) propertyValue; 181 if (idx < docProperty.size()) { 182 subProperty = docProperty.get((Integer) propertyValue); 183 } 184 } else if (docProperty instanceof ComplexProperty && propertyValue instanceof String) { 185 subProperty = docProperty.get((String) propertyValue); 186 } 187 if (subProperty == null) { 188 throw new PropertyException(String.format("Could not resolve subproperty '%s' under '%s'", propertyValue, 189 docProperty.getXPath())); 190 } 191 return subProperty; 192 } 193 194 private static Object getDocumentPropertyValue(Property docProperty) throws PropertyException { 195 if (docProperty == null) { 196 throw new PropertyException("Null property"); 197 } 198 Object value = docProperty; 199 if (!docProperty.isContainer()) { 200 // return the value 201 value = docProperty.getValue(); 202 value = FieldAdapterManager.getValueForDisplay(value); 203 } 204 return value; 205 } 206 207 /** 208 * Handle property mappings for blobs. The Blob use case is handled here too instead of a dedicated EL resolver to 209 * avoid multiplying resolvers in the chain. 210 */ 211 private static Object getBlobMapping(Object property) throws PropertyException { 212 Object prop = property; 213 if ("name".equals(property)) { 214 prop = "filename"; 215 } else if ("mime-type".equals(property)) { 216 prop = "mimeType"; 217 } 218 return prop; 219 } 220 221 @Override 222 public boolean isReadOnly(ELContext context, Object base, Object property) { 223 boolean readOnly = false; 224 try { 225 readOnly = super.isReadOnly(context, base, property); 226 } catch (PropertyNotFoundException e) { 227 if (base instanceof DocumentModel || base instanceof DocumentPropertyContext) { 228 // readOnly is false 229 context.setPropertyResolved(true); 230 } else if (base instanceof Property) { 231 readOnly = ((Property) base).isReadOnly(); 232 context.setPropertyResolved(true); 233 } 234 } 235 return readOnly; 236 } 237 238 @Override 239 @SuppressWarnings("unchecked") 240 public void setValue(ELContext context, Object base, Object property, Object value) { 241 if (base instanceof DocumentModel) { 242 try { 243 super.setValue(context, base, property, value); 244 } catch (PropertyNotFoundException e) { 245 // nothing else to set on doc model 246 } 247 } else if (base instanceof DocumentPropertyContext) { 248 DocumentPropertyContext ctx = (DocumentPropertyContext) base; 249 value = FieldAdapterManager.getValueForStorage(value); 250 try { 251 if ("files".equals(ctx.getSchema())) { 252 // Remove possible null values if a file was deleted 253 List<Map<String, Serializable>> files = (List<Map<String, Serializable>>) value; 254 List<Map<String, Serializable>> filteredFiles = files.stream() 255 .filter(file -> file.get("file") != null) 256 .collect(Collectors.toList()); 257 ctx.doc.setPropertyValue(getDocumentPropertyName(ctx, property), (Serializable) filteredFiles); 258 } else { 259 ctx.doc.setPropertyValue(getDocumentPropertyName(ctx, property), (Serializable) value); 260 } 261 } catch (PropertyException e) { 262 // avoid errors here too 263 log.warn(e.toString()); 264 } 265 context.setPropertyResolved(true); 266 } else if (base instanceof Property) { 267 try { 268 Property docProperty = (Property) base; 269 // Remove possible null values if a file was deleted 270 if ("files".equals(docProperty.getSchema().getName()) && value == null) { 271 docProperty.remove(); 272 } else { 273 Property subProperty = getDocumentProperty(docProperty, property); 274 value = FieldAdapterManager.getValueForStorage(value); 275 subProperty.setValue(value); 276 } 277 } catch (PropertyException pe) { 278 try { 279 // try property setters to resolve doc.schema.field.type 280 // for instance 281 super.setValue(context, base, property, value); 282 } catch (PropertyNotFoundException e) { 283 // log original error and avoid errors here too 284 log.warn(pe.toString()); 285 } 286 } 287 context.setPropertyResolved(true); 288 } else if (base instanceof Blob) { 289 super.setValue(context, base, getBlobMapping(property), value); 290 } 291 } 292 293}