001/* 002 * (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Nuxeo - initial API and implementation 016 * 017 * $Id: DocumentModelResolver.java 23589 2007-08-08 16:50:40Z fguillaume $ 018 */ 019 020package org.nuxeo.ecm.platform.el; 021 022import java.io.Serializable; 023import java.util.List; 024 025import javax.el.BeanELResolver; 026import javax.el.ELContext; 027import javax.el.PropertyNotFoundException; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031import org.nuxeo.ecm.core.api.DocumentModel; 032import org.nuxeo.ecm.core.api.PropertyException; 033import org.nuxeo.ecm.core.api.model.Property; 034import org.nuxeo.ecm.core.api.model.impl.ArrayProperty; 035import org.nuxeo.ecm.core.api.model.impl.ComplexProperty; 036import org.nuxeo.ecm.core.api.model.impl.ListProperty; 037 038/** 039 * Resolves expressions for the {@link DocumentModel} framework. 040 * <p> 041 * To specify a property on a document mode, the following syntax is available: 042 * <code>myDocumentModel.dublincore.title</code> where 'dublincore' is the schema name and 'title' is the field name. It 043 * can be used to get or set the document title: {@code <h:outputText value="# {currentDocument.dublincore.title}" />} 044 * or {@code <h:inputText value="# {currentDocument.dublincore.title}" />}. 045 * <p> 046 * Simple document properties are get/set directly: for instance, the above expression will return a String value on 047 * get, and set this String on the document for set. Complex properties (maps and lists) are get/set through the 048 * {@link Property} object controlling their value: on get, sub properties will be resolved at the next iteration, and 049 * on set, they will be set on the property instance so the document model is aware of the change. 050 * 051 * @author <a href="mailto:rcaraghin@nuxeo.com">Razvan Caraghin</a> 052 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a> 053 */ 054public class DocumentModelResolver extends BeanELResolver { 055 056 private static final Log log = LogFactory.getLog(DocumentModelResolver.class); 057 058 // XXX AT: see if getFeatureDescriptor needs to be overloaded to return 059 // datamodels descriptors. 060 061 @Override 062 public Class<?> getType(ELContext context, Object base, Object property) { 063 Class<?> type = null; 064 if (base instanceof DocumentModel) { 065 try { 066 type = super.getType(context, base, property); 067 } catch (PropertyNotFoundException e) { 068 type = DocumentPropertyContext.class; 069 context.setPropertyResolved(true); 070 } 071 } else if (base instanceof DocumentPropertyContext || base instanceof Property) { 072 type = Object.class; 073 if (base instanceof DocumentPropertyContext) { 074 DocumentPropertyContext ctx = (DocumentPropertyContext) base; 075 try { 076 Property docProperty = getDocumentProperty(ctx, property); 077 if (docProperty.isContainer()) { 078 Property subProperty = getDocumentProperty(docProperty, property); 079 if (subProperty.isList()) { 080 type = List.class; 081 } 082 } else if (docProperty instanceof ArrayProperty) { 083 type = List.class; 084 } 085 } catch (PropertyException pe) { 086 // avoid errors, return Object 087 log.warn(pe.toString()); 088 } 089 } else if (base instanceof Property) { 090 try { 091 Property docProperty = (Property) base; 092 Property subProperty = getDocumentProperty(docProperty, property); 093 if (subProperty.isList()) { 094 type = List.class; 095 } 096 } catch (PropertyException pe) { 097 try { 098 // try property getters to resolve 099 // doc.schema.field.type for instance 100 type = super.getType(context, base, property); 101 } catch (PropertyNotFoundException e) { 102 // avoid errors, log original error and return Object 103 log.warn(pe.toString()); 104 } 105 } 106 } 107 context.setPropertyResolved(true); 108 } 109 return type; 110 } 111 112 @Override 113 public Object getValue(ELContext context, Object base, Object property) { 114 Object value = null; 115 if (base instanceof DocumentModel) { 116 try { 117 // try document getters first to resolve doc.id for instance 118 value = super.getValue(context, base, property); 119 } catch (PropertyNotFoundException e) { 120 value = new DocumentPropertyContext((DocumentModel) base, (String) property); 121 context.setPropertyResolved(true); 122 } 123 } else if (base instanceof DocumentPropertyContext) { 124 try { 125 DocumentPropertyContext ctx = (DocumentPropertyContext) base; 126 Property docProperty = getDocumentProperty(ctx, property); 127 value = getDocumentPropertyValue(docProperty); 128 } catch (PropertyException pe) { 129 // avoid errors, return null 130 log.warn(pe.toString()); 131 } 132 context.setPropertyResolved(true); 133 } else if (base instanceof Property) { 134 try { 135 Property docProperty = (Property) base; 136 Property subProperty = getDocumentProperty(docProperty, property); 137 value = getDocumentPropertyValue(subProperty); 138 } catch (PropertyException pe) { 139 try { 140 // try property getters to resolve doc.schema.field.type 141 // for instance 142 value = super.getValue(context, base, property); 143 } catch (PropertyNotFoundException e) { 144 // avoid errors, log original error and return null 145 log.warn(pe.toString()); 146 } 147 } 148 context.setPropertyResolved(true); 149 } 150 151 return value; 152 } 153 154 private static String getDocumentPropertyName(DocumentPropertyContext ctx, Object propertyValue) { 155 return ctx.schema + ":" + propertyValue; 156 } 157 158 private static Property getDocumentProperty(DocumentPropertyContext ctx, Object propertyValue) 159 throws PropertyException { 160 return ctx.doc.getProperty(getDocumentPropertyName(ctx, propertyValue)); 161 } 162 163 @SuppressWarnings("boxing") 164 private static Property getDocumentProperty(Property docProperty, Object propertyValue) throws PropertyException { 165 Property subProperty = null; 166 if ((docProperty instanceof ArrayProperty || docProperty instanceof ListProperty) 167 && propertyValue instanceof Long) { 168 subProperty = docProperty.get(((Long) propertyValue).intValue()); 169 } else if ((docProperty instanceof ArrayProperty || docProperty instanceof ListProperty) 170 && propertyValue instanceof Integer) { 171 Integer idx = (Integer) propertyValue; 172 if (idx < docProperty.size()) { 173 subProperty = docProperty.get((Integer) propertyValue); 174 } 175 } else if (docProperty instanceof ComplexProperty && propertyValue instanceof String) { 176 subProperty = docProperty.get((String) propertyValue); 177 } 178 if (subProperty == null) { 179 throw new PropertyException(String.format("Could not resolve subproperty '%s' under '%s'", propertyValue, 180 docProperty.getPath())); 181 } 182 return subProperty; 183 } 184 185 private static Object getDocumentPropertyValue(Property docProperty) throws PropertyException { 186 if (docProperty == null) { 187 throw new PropertyException("Null property"); 188 } 189 Object value = docProperty; 190 if (!docProperty.isContainer()) { 191 // return the value 192 value = docProperty.getValue(); 193 value = FieldAdapterManager.getValueForDisplay(value); 194 } 195 return value; 196 } 197 198 @Override 199 public boolean isReadOnly(ELContext context, Object base, Object property) { 200 boolean readOnly = false; 201 try { 202 readOnly = super.isReadOnly(context, base, property); 203 } catch (PropertyNotFoundException e) { 204 if (base instanceof DocumentModel || base instanceof DocumentPropertyContext) { 205 readOnly = false; 206 context.setPropertyResolved(true); 207 } else if (base instanceof Property) { 208 readOnly = ((Property) base).isReadOnly(); 209 context.setPropertyResolved(true); 210 } 211 } 212 return readOnly; 213 } 214 215 @Override 216 public void setValue(ELContext context, Object base, Object property, Object value) { 217 if (base instanceof DocumentModel) { 218 try { 219 super.setValue(context, base, property, value); 220 } catch (PropertyNotFoundException e) { 221 // nothing else to set on doc model 222 } 223 } else if (base instanceof DocumentPropertyContext) { 224 DocumentPropertyContext ctx = (DocumentPropertyContext) base; 225 value = FieldAdapterManager.getValueForStorage(value); 226 try { 227 ctx.doc.setPropertyValue(getDocumentPropertyName(ctx, property), (Serializable) value); 228 } catch (PropertyException e) { 229 // avoid errors here too 230 log.warn(e.toString()); 231 } 232 context.setPropertyResolved(true); 233 } else if (base instanceof Property) { 234 try { 235 Property docProperty = (Property) base; 236 Property subProperty = getDocumentProperty(docProperty, property); 237 value = FieldAdapterManager.getValueForStorage(value); 238 subProperty.setValue(value); 239 } catch (PropertyException pe) { 240 try { 241 // try property setters to resolve doc.schema.field.type 242 // for instance 243 super.setValue(context, base, property, value); 244 } catch (PropertyNotFoundException e) { 245 // log original error and avoid errors here too 246 log.warn(pe.toString()); 247 } 248 } 249 context.setPropertyResolved(true); 250 } 251 } 252 253}