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}