001/*
002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003 *
004 * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
005 *
006 * The contents of this file are subject to the terms of either the GNU
007 * General Public License Version 2 only ("GPL") or the Common Development
008 * and Distribution License("CDDL") (collectively, the "License").  You
009 * may not use this file except in compliance with the License.  You can
010 * obtain a copy of the License at
011 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
012 * or packager/legal/LICENSE.txt.  See the License for the specific
013 * language governing permissions and limitations under the License.
014 *
015 * When distributing the software, include this License Header Notice in each
016 * file and include the License file at packager/legal/LICENSE.txt.
017 *
018 * GPL Classpath Exception:
019 * Oracle designates this particular file as subject to the "Classpath"
020 * exception as provided by Oracle in the GPL Version 2 section of the License
021 * file that accompanied this code.
022 *
023 * Modifications:
024 * If applicable, add the following below the License Header, with the fields
025 * enclosed by brackets [] replaced by your own identifying information:
026 * "Portions Copyright [year] [name of copyright owner]"
027 *
028 * Contributor(s):
029 * If you wish your version of this file to be governed by only the CDDL or
030 * only the GPL Version 2, indicate your decision by adding "[Contributor]
031 * elects to include this software in this distribution under the [CDDL or GPL
032 * Version 2] license."  If you don't indicate a single choice of license, a
033 * recipient has the option to distribute your version of this file under
034 * either the CDDL, the GPL Version 2 or to extend the choice of license to
035 * its licensees as provided above.  However, if you add GPL Version 2 code
036 * and therefore, elected the GPL Version 2 license, then the option applies
037 * only if the new code is made subject to such option by the copyright
038 * holder.
039 *
040 * Contributors:
041 *     Anahide Tchertchian
042 */
043
044package org.nuxeo.ecm.platform.ui.web.validator;
045
046import java.beans.FeatureDescriptor;
047import java.util.Iterator;
048import java.util.Locale;
049
050import javax.el.ELContext;
051import javax.el.ELException;
052import javax.el.ELResolver;
053import javax.el.FunctionMapper;
054import javax.el.ValueExpression;
055import javax.el.ValueReference;
056import javax.el.VariableMapper;
057import javax.faces.el.CompositeComponentExpressionHolder;
058
059import org.nuxeo.ecm.platform.ui.web.model.ProtectedEditableModel;
060
061/**
062 * Analyzes a {@link ValueExpression} and provides access to the base object and property name to which the expression
063 * maps via the getReference() method.
064 *
065 * @since 7.2
066 */
067public class ValueExpressionAnalyzer {
068
069    private ValueExpression expression;
070
071    public ValueExpressionAnalyzer(ValueExpression expression) {
072        this.expression = expression;
073    }
074
075    public ValueReference getReference(ELContext elContext) {
076        InterceptingResolver resolver = new InterceptingResolver(elContext.getELResolver());
077        try {
078            expression.setValue(decorateELContext(elContext, resolver), null);
079        } catch (ELException ele) {
080            return null;
081        }
082        ValueReference reference = resolver.getValueReference();
083        if (reference != null) {
084            Object base = reference.getBase();
085            if (base instanceof CompositeComponentExpressionHolder) {
086                Object prop = reference.getProperty();
087                if (prop instanceof String) {
088                    ValueExpression ve = ((CompositeComponentExpressionHolder) base).getExpression((String) prop);
089                    if (ve != null) {
090                        this.expression = ve;
091                        reference = getReference(elContext);
092                    }
093                }
094            }
095        }
096        return reference;
097    }
098
099    private ELContext decorateELContext(final ELContext context, final ELResolver resolver) {
100        return new ELContext() {
101
102            // punch in our new ELResolver
103            @Override
104            public ELResolver getELResolver() {
105                return resolver;
106            }
107
108            // The rest of the methods simply delegate to the existing context
109
110            @Override
111            @SuppressWarnings("rawtypes")
112            public Object getContext(Class key) {
113                return context.getContext(key);
114            }
115
116            @Override
117            public Locale getLocale() {
118                return context.getLocale();
119            }
120
121            @Override
122            public boolean isPropertyResolved() {
123                return context.isPropertyResolved();
124            }
125
126            @Override
127            @SuppressWarnings("rawtypes")
128            public void putContext(Class key, Object contextObject) {
129                context.putContext(key, contextObject);
130            }
131
132            @Override
133            public void setLocale(Locale locale) {
134                context.setLocale(locale);
135            }
136
137            @Override
138            public void setPropertyResolved(boolean resolved) {
139                context.setPropertyResolved(resolved);
140            }
141
142            @Override
143            public FunctionMapper getFunctionMapper() {
144                return context.getFunctionMapper();
145            }
146
147            @Override
148            public VariableMapper getVariableMapper() {
149                return context.getVariableMapper();
150            }
151        };
152    }
153
154    static class ListItemMapper {
155
156        protected ProtectedEditableModel model;
157
158        protected Object value;
159
160        public ListItemMapper(ProtectedEditableModel model, Object value) {
161            super();
162            this.model = model;
163            this.value = value;
164        }
165
166        public ProtectedEditableModel getModel() {
167            return model;
168        }
169
170        public Object getValue() {
171            return value;
172        }
173
174        @Override
175        public String toString() {
176            final StringBuilder buf = new StringBuilder();
177            buf.append("ListItemMapper");
178            buf.append(" {");
179            buf.append(" model=");
180            buf.append(model);
181            buf.append(", value=");
182            buf.append(value);
183            buf.append('}');
184            return buf.toString();
185        }
186
187    }
188
189    private static class InterceptingResolver extends ELResolver {
190
191        private ELResolver delegate;
192
193        private ValueReference valueReference;
194
195        public InterceptingResolver(ELResolver delegate) {
196            this.delegate = delegate;
197        }
198
199        public ValueReference getValueReference() {
200            return valueReference;
201        }
202
203        // Capture the base and property rather than write the value
204        @Override
205        public void setValue(ELContext context, Object base, Object property, Object value) {
206            if (base != null && property != null) {
207                context.setPropertyResolved(true);
208                valueReference = new ValueReference(base, property.toString());
209            }
210        }
211
212        // The rest of the methods simply delegate to the existing context
213
214        @Override
215        public Object getValue(ELContext context, Object base, Object property) {
216            Object value = delegate.getValue(context, base, property);
217            if (base != null && ProtectedEditableModel.class.isAssignableFrom(base.getClass())
218                    && "rowData".equals(property)) {
219                // mapping a list element => wrap result
220                return new ListItemMapper((ProtectedEditableModel) base, value);
221            }
222            return value;
223        }
224
225        @Override
226        public Class<?> getType(ELContext context, Object base, Object property) {
227            return delegate.getType(context, base, property);
228        }
229
230        @Override
231        public boolean isReadOnly(ELContext context, Object base, Object property) {
232            return delegate.isReadOnly(context, base, property);
233        }
234
235        @Override
236        public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
237            return delegate.getFeatureDescriptors(context, base);
238        }
239
240        @Override
241        public Class<?> getCommonPropertyType(ELContext context, Object base) {
242            return delegate.getCommonPropertyType(context, base);
243        }
244
245    }
246}