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