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}