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