001    // Copyright 2004, 2005 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry.binding;
016    
017    import ognl.Node;
018    import ognl.enhance.ExpressionAccessor;
019    import org.apache.hivemind.Location;
020    import org.apache.tapestry.BindingException;
021    import org.apache.tapestry.IComponent;
022    import org.apache.tapestry.coerce.ValueConverter;
023    import org.apache.tapestry.services.ExpressionCache;
024    import org.apache.tapestry.services.ExpressionEvaluator;
025    
026    /**
027     * Implements a dynamic binding, based on evaluating an expression using an expression language.
028     * Tapestry's default expression language is the <a href="http://www.ognl.org/">Object Graph
029     * Navigation Language </a>.
030     *
031     * @see org.apache.tapestry.services.ExpressionEvaluator
032     * @author Howard Lewis Ship
033     * @since 2.2
034     */
035    
036    public class ExpressionBinding extends AbstractBinding
037    {
038        /**
039         * The root object against which the nested property name is evaluated.
040         */
041    
042        private final IComponent _root;
043    
044        /**
045         * If true, then the binding is invariant.
046         */
047    
048        private boolean _invariant = false;
049    
050        /**
051         * Parsed OGNL expression.
052         */
053    
054        private Node _parsedExpression;
055    
056        /**
057         * Compiled OGNL expression.
058         */
059    
060        private ExpressionAccessor _accessor;
061    
062        /**
063         * Flag set to true once the binding has initialized.
064         */
065    
066        private boolean _initialized;
067    
068        /**
069         * @since 4.0
070         */
071    
072        private ExpressionEvaluator _evaluator;
073    
074        /** @since 4.0 */
075    
076        private ExpressionCache _cache;
077    
078        /**
079         * Used to detect previous failed attempts at writing values when compiling expressions so
080         * that as many expressions as possible can be fully compiled into their java byte form when
081         * all objects in the expression are available.
082         */
083        private boolean _writeFailed;
084    
085        /**
086         * Creates a {@link ExpressionBinding} from the root object and an OGNL expression.
087         *
088         * @param description
089         *          Used by superclass constructor - {@link AbstractBinding#AbstractBinding(String, org.apache.tapestry.coerce.ValueConverter, org.apache.hivemind.Location)}.
090         * @param location
091         *          Used by superclass constructor - {@link AbstractBinding#AbstractBinding(String, org.apache.tapestry.coerce.ValueConverter, org.apache.hivemind.Location)}.
092         * @param valueConverter
093         *          Used by superclass constructor - {@link AbstractBinding#AbstractBinding(String, org.apache.tapestry.coerce.ValueConverter, org.apache.hivemind.Location)}.
094         * @param root
095         *          The object this binding should be resolved against.
096         * @param expression
097         *          The string expression.
098         * @param evaluator
099         *          Evaluator used to parse and run the expression.
100         * @param cache
101         *          Expression cache which does efficient caching of parsed expressions.
102         */
103        public ExpressionBinding(String description, Location location, ValueConverter valueConverter,
104                                 IComponent root, String expression, ExpressionEvaluator evaluator,
105                                 ExpressionCache cache)
106        {
107            super(expression, valueConverter, location);
108    
109            _root = root;
110            _evaluator = evaluator;
111            _cache = cache;
112        }
113    
114        /**
115         * Gets the value of the property path, with the assistance of the {@link ExpressionEvaluator}.
116         *
117         * @throws BindingException
118         *             if an exception is thrown accessing the property.
119         */
120    
121        public Object getObject()
122        {
123            initialize();
124    
125            return resolveExpression();
126        }
127    
128        private Object resolveExpression()
129        {
130            try
131            {
132                if (_accessor == null && !_writeFailed)
133                {
134                    _parsedExpression = (Node)_cache.getCompiledExpression(_root, _description);
135                    _accessor = _parsedExpression.getAccessor();
136                }
137    
138                if (_accessor != null)
139                    return _evaluator.read(_root, _accessor);
140    
141                return _evaluator.readCompiled(_root, _parsedExpression);
142            }
143            catch (Throwable t)
144            {
145                throw new BindingException(t.getMessage(), this, t);
146            }
147        }
148    
149        /**
150         * Returns true if the binding is expected to always return the same value.
151         */
152    
153        public boolean isInvariant()
154        {
155            initialize();
156    
157            return _invariant;
158        }
159    
160        /**
161         * Sets up the helper object, but also optimizes the property path and determines if the binding
162         * is invarant.
163         */
164    
165        private void initialize()
166        {
167            if (_initialized)
168                return;
169    
170            _initialized = true;
171    
172            try
173            {
174                _parsedExpression = (Node)_cache.getCompiledExpression(_description);
175                _invariant = _evaluator.isConstant(_description);
176            }
177            catch (Exception ex)
178            {
179                throw new BindingException(ex.getMessage(), this, ex);
180            }
181        }
182    
183        /**
184         * Updates the property for the binding to the given value.
185         *
186         * @throws BindingException
187         *             if the property can't be updated (typically due to an security problem, or a
188         *             missing mutator method).
189         */
190    
191        public void setObject(Object value)
192        {
193            initialize();
194    
195            if (_invariant)
196                throw createReadOnlyBindingException(this);
197    
198            try
199            {
200                if (_accessor == null)
201                {
202                    _evaluator.writeCompiled(_root, _parsedExpression, value);
203    
204                    if (!_writeFailed)
205                    {    
206                        // re-parse expression as compilation may be possible now that it potentially has a value
207                        try {
208                            _parsedExpression = (Node)_cache.getCompiledExpression(_root, _description);
209    
210                            _accessor = _parsedExpression.getAccessor();
211                        } catch (Throwable t) {
212    
213                            // ignore re-read failures as they aren't supposed to be happening now anyways
214                            // and a more user friendly version will be available if someone actually calls
215                            // getObject
216    
217                            // if writing fails then we're probably screwed...so don't do it again
218                            if (value != null)
219                                _writeFailed = true;
220                        }
221                    }
222                } else
223                {
224                    _evaluator.write(_root, _accessor, value);
225                }
226            }
227            catch (Throwable ex)
228            {
229                throw new BindingException(ex.getMessage(), this, ex);
230            }
231        }
232    
233        /**
234         * Returns the a String representing the property path. This includes the
235         * {@link IComponent#getExtendedId() extended id}of the root component and the property path
236         * ... once the binding is used, these may change due to optimization of the property path.
237         */
238    
239        public String toString()
240        {
241            StringBuffer buffer = new StringBuffer();
242    
243            buffer.append("ExpressionBinding[");
244            buffer.append(_root.getExtendedId());
245    
246            if (_description != null)
247            {
248                buffer.append(' ');
249                buffer.append(_description);
250            }
251    
252            buffer.append(']');
253    
254            return buffer.toString();
255        }
256    
257        /** @since 4.0 */
258        public Object getComponent()
259        {
260            return _root;
261        }
262    }