<commit_before>/*
 * (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl.html
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * Contributors:
 *     Nuxeo - initial API and implementation
 *
 * $Id: DocumentModelResolver.java 23589 2007-08-08 16:50:40Z fguillaume $
 */

package org.nuxeo.ecm.platform.ui.web.resolver;

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.model.Property;
import org.nuxeo.ecm.core.api.model.PropertyException;
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 : <h:outputText value="#{currentDocument.dublincore.title}" />
 * or <h:inputText value="#{currentDocument.dublincore.title}" />.
 * </p>
 * <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.
 * </p>
 *
 * @author <a href="mailto:<EMAIL>"><NAME></a>
 * @author <a href="mailto:<EMAIL>"><NAME></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;
            context.setPropertyResolved(true);
        }
        return type;
    }

    @Override
    public Object getValue(ELContext context, Object base, Object property) {
        boolean avoidErrors = false;
        String errorMessage = null;
        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 = ctx.doc.getProperty(ctx.schema + ":"
                        + (String) property);
                value = getDocumentPropertyValue(docProperty);
                context.setPropertyResolved(true);
            } catch (PropertyException pe) {
                // avoid errors, return null
                avoidErrors = true;
                errorMessage = pe.getMessage();
            }
        } else if (base instanceof Property) {
            try {
                Property docProperty = (Property) base;
                Property subProperty = getDocumentProperty(docProperty,
                        property);
                value = getDocumentPropertyValue(subProperty);
                context.setPropertyResolved(true);
            } catch (PropertyException pe) {
                // avoid errors, return null
                avoidErrors = true;
                errorMessage = pe.getMessage();
            }
        }

        // XXX end by bean resolver to overcome a mysterious StackOverflow error
        // at first login, and maybe resolve Property getters.
        if (!context.isPropertyResolved()) {
            try {
                value = super.getValue(context, base, property);
            } catch (Exception e) {
                context.setPropertyResolved(false);
            }
        }

        if (!context.isPropertyResolved() && avoidErrors) {
            log.warn(errorMessage);
            context.setPropertyResolved(true);
        }

        return value;
    }

    private 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) {
            subProperty = docProperty.get((Integer) propertyValue);
        } else if (docProperty instanceof ComplexProperty
                && propertyValue instanceof String) {
            subProperty = docProperty.get((String) propertyValue);
        }
        if (subProperty == null) {
            throw new PropertyException("Property not found");
        }
        return subProperty;
    }

    private Object getDocumentPropertyValue(Property docProperty)
            throws PropertyException {<commit_msg>more informative exception message in DocumentModelResolver.getDocumentProperty(...)<commit_after>/*
 * (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl.html
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * Contributors:
 *     Nuxeo - initial API and implementation
 *
 * $Id: DocumentModelResolver.java 23589 2007-08-08 16:50:40Z fguillaume $
 */

package org.nuxeo.ecm.platform.ui.web.resolver;

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.model.Property;
import org.nuxeo.ecm.core.api.model.PropertyException;
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 : <h:outputText value="#{currentDocument.dublincore.title}" />
 * or <h:inputText value="#{currentDocument.dublincore.title}" />.
 * </p>
 * <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.
 * </p>
 *
 * @author <a href="mailto:<EMAIL>"><NAME></a>
 * @author <a href="mailto:<EMAIL>"><NAME></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;
            context.setPropertyResolved(true);
        }
        return type;
    }

    @Override
    public Object getValue(ELContext context, Object base, Object property) {
        boolean avoidErrors = false;
        String errorMessage = null;
        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 = ctx.doc.getProperty(ctx.schema + ":"
                        + (String) property);
                value = getDocumentPropertyValue(docProperty);
                context.setPropertyResolved(true);
            } catch (PropertyException pe) {
                // avoid errors, return null
                avoidErrors = true;
                errorMessage = pe.getMessage();
            }
        } else if (base instanceof Property) {
            try {
                Property docProperty = (Property) base;
                Property subProperty = getDocumentProperty(docProperty,
                        property);
                value = getDocumentPropertyValue(subProperty);
                context.setPropertyResolved(true);
            } catch (PropertyException pe) {
                // avoid errors, return null
                avoidErrors = true;
                errorMessage = pe.getMessage();
            }
        }

        // XXX end by bean resolver to overcome a mysterious StackOverflow error
        // at first login, and maybe resolve Property getters.
        if (!context.isPropertyResolved()) {
            try {
                value = super.getValue(context, base, property);
            } catch (Exception e) {
                context.setPropertyResolved(false);
            }
        }

        if (!context.isPropertyResolved() && avoidErrors) {
            log.warn(errorMessage);
            context.setPropertyResolved(true);
        }

        return value;
    }

    private 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) {
            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.getPath()));
        }
        return subProperty;
    }

    private Object getDocumentPropertyValue(Property docProperty)
            throws PropertyException {
{'fraction_non_alphanumeric': 0.08667856080880167, 'fraction_numerical': 0.004311626523936961, 'mean_word_length': 2.443306885078065, 'pattern_counts': {'":': 2, '<': 39, '<?xml version=': 0, '>': 39, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2410587', 'n_tokens_mistral': 3383, 'n_tokens_neox': 3242, 'n_words': 1050}