/*
* (C) Copyright 2006-2007 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Nuxeo - initial API and implementation
*
* $Id: DocumentModelResolver.java 23589 2007-08-08 16:50:40Z fguillaume $
*/
package org.nuxeo.ecm.platform.el;
import java.io.Serializable;
import java.util.List;
import javax.el.BeanELResolver;
import javax.el.ELContext;
import javax.el.PropertyNotFoundException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.PropertyException;
import org.nuxeo.ecm.core.api.model.Property;
import org.nuxeo.ecm.core.api.model.impl.ArrayProperty;
import org.nuxeo.ecm.core.api.model.impl.ComplexProperty;
import org.nuxeo.ecm.core.api.model.impl.ListProperty;
/**
* Resolves expressions for the {@link DocumentModel} framework.
* <p>
* To specify a property on a document mode, the following syntax is available:
* <code>myDocumentModel.dublincore.title</code> where 'dublincore' is the schema name and 'title' is the field name. It
* can be used to get or set the document title: {@code <h:outputText value="# {currentDocument.dublincore.title}" />}
* or {@code <h:inputText value="# {currentDocument.dublincore.title}" />}.
* <p>
* Simple document properties are get/set directly: for instance, the above expression will return a String value on
* get, and set this String on the document for set. Complex properties (maps and lists) are get/set through the
* {@link Property} object controlling their value: on get, sub properties will be resolved at the next iteration, and
* on set, they will be set on the property instance so the document model is aware of the change.
*
* @author <a href="mailto:rcaraghin@nuxeo.com">Razvan Caraghin</a>
* @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
*/
public class DocumentModelResolver extends BeanELResolver {
private static final Log log = LogFactory.getLog(DocumentModelResolver.class);
// XXX AT: see if getFeatureDescriptor needs to be overloaded to return
// datamodels descriptors.
@Override
public Class<?> getType(ELContext context, Object base, Object property) {
Class<?> type = null;
if (base instanceof DocumentModel) {
try {
type = super.getType(context, base, property);
} catch (PropertyNotFoundException e) {
type = DocumentPropertyContext.class;
context.setPropertyResolved(true);
}
} else if (base instanceof DocumentPropertyContext || base instanceof Property) {
type = Object.class;
if (base instanceof DocumentPropertyContext) {
DocumentPropertyContext ctx = (DocumentPropertyContext) base;
try {
Property docProperty = getDocumentProperty(ctx, property);
if (docProperty.isContainer()) {
Property subProperty = getDocumentProperty(docProperty, property);
if (subProperty.isList()) {
type = List.class;
}
} else if (docProperty instanceof ArrayProperty) {
type = List.class;
}
} catch (PropertyException pe) {
// avoid errors, return Object
log.warn(pe.toString());
}
} else if (base instanceof Property) {
try {
Property docProperty = (Property) base;
Property subProperty = getDocumentProperty(docProperty, property);
if (subProperty.isList()) {
type = List.class;
}
} catch (PropertyException pe) {
try {
// try property getters to resolve
// doc.schema.field.type for instance
type = super.getType(context, base, property);
} catch (PropertyNotFoundException e) {
// avoid errors, log original error and return Object
log.warn(pe.toString());
}
}
}
context.setPropertyResolved(true);
}
return type;
}
@Override
public Object getValue(ELContext context, Object base, Object property) {
Object value = null;
if (base instanceof DocumentModel) {
try {
// try document getters first to resolve doc.id for instance
value = super.getValue(context, base, property);
} catch (PropertyNotFoundException e) {
value = new DocumentPropertyContext((DocumentModel) base, (String) property);
context.setPropertyResolved(true);
}
} else if (base instanceof DocumentPropertyContext) {
try {
DocumentPropertyContext ctx = (DocumentPropertyContext) base;
Property docProperty = getDocumentProperty(ctx, property);
value = getDocumentPropertyValue(docProperty);
} catch (PropertyException pe) {
// avoid errors, return null
log.warn(pe.toString());
}
context.setPropertyResolved(true);
} else if (base instanceof Property) {
try {
Property docProperty = (Property) base;
Property subProperty = getDocumentProperty(docProperty, property);
value = getDocumentPropertyValue(subProperty);
} catch (PropertyException pe) {
try {
// try property getters to resolve doc.schema.field.type
// for instance
value = super.getValue(context, base, property);
} catch (PropertyNotFoundException e) {
// avoid errors, log original error and return null
log.warn(pe.toString());
}
}
context.setPropertyResolved(true);
}
return value;
}
private static String getDocumentPropertyName(DocumentPropertyContext ctx, Object propertyValue) {
return ctx.schema + ":" + propertyValue;
}
private static Property getDocumentProperty(DocumentPropertyContext ctx, Object propertyValue)
throws PropertyException {
return ctx.doc.getProperty(getDocumentPropertyName(ctx, propertyValue));
}
@SuppressWarnings("boxing")
private static Property getDocumentProperty(Property docProperty, Object propertyValue) throws PropertyException {
Property subProperty = null;
if ((docProperty instanceof ArrayProperty || docProperty instanceof ListProperty)
&& propertyValue instanceof Long) {
subProperty = docProperty.get(((Long) propertyValue).intValue());
} else if ((docProperty instanceof ArrayProperty || docProperty instanceof ListProperty)
&& propertyValue instanceof Integer) {
Integer idx = (Integer) propertyValue;
if (idx < docProperty.size()) {
subProperty = docProperty.get((Integer) propertyValue);
}
} else if (docProperty instanceof ComplexProperty && propertyValue instanceof String) {
subProperty = docProperty.get((String) propertyValue);
}
if (subProperty == null) {
throw new PropertyException(String.format("Could not resolve subproperty '%s' under '%s'", propertyValue,
docProperty.getXPath()));
}
return subProperty;
}
private static Object getDocumentPropertyValue(Property docProperty) throws PropertyException {
if (docProperty == null) {
throw new PropertyException("Null property");
}
Object value = docProperty;
if (!docProperty.isContainer()) {
// return the value
value = docProperty.getValue();
value = FieldAdapterManager.getValueForDisplay(value);
}
return value;
}
@Override
public boolean isReadOnly(ELContext context, Object base, Object property) {
boolean readOnly = false;
try {
readOnly = super.isReadOnly(context, base, property);
} catch (PropertyNotFoundException e) {
if (base instanceof DocumentModel || base instanceof DocumentPropertyContext) {
readOnly = false;
context.setPropertyResolved(true);
} else if (base instanceof Property) {
readOnly = ((Property) base).isReadOnly();
context.setPropertyResolved(true);
}
}
return readOnly;
}
@Override
public void setValue(ELContext context, Object base, Object property, Object value) {
if (base instanceof DocumentModel) {
try {
super.setValue(context, base, property, value);
} catch (PropertyNotFoundException e) {
// nothing else to set on doc model
}
} else if (base instanceof DocumentPropertyContext) {
DocumentPropertyContext ctx = (DocumentPropertyContext) base;
value = FieldAdapterManager.getValueForStorage(value);
try {
ctx.doc.setPropertyValue(getDocumentPropertyName(ctx, property), (Serializable) value);
} catch (PropertyException e) {
// avoid errors here too
log.warn(e.toString());
}
context.setPropertyResolved(true);
} else if (base instanceof Property) {
try {
Property docProperty = (Property) base;
Property subProperty = getDocumentProperty(docProperty, property);
value = FieldAdapterManager.getValueForStorage(value);
subProperty.setValue(value);
} catch (PropertyException pe) {
try {
// try property setters to resolve doc.schema.field.type
// for instance
super.setValue(context, base, property, value);
} catch (PropertyNotFoundException e) {
// log original error and avoid errors here too
log.warn(pe.toString());
}
}
context.setPropertyResolved(true);
}
}
}