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}