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 org.apache.hivemind.Location;
018    import org.apache.tapestry.BindingException;
019    import org.apache.tapestry.IComponent;
020    import org.apache.tapestry.coerce.ValueConverter;
021    import org.apache.tapestry.services.ExpressionCache;
022    import org.apache.tapestry.services.ExpressionEvaluator;
023    
024    /**
025     * Implements a dynamic binding, based on evaluating an expression using an expression language.
026     * Tapestry's default expression language is the <a href="http://www.ognl.org/">Object Graph
027     * Navigation Language </a>.
028     * 
029     * @see org.apache.tapestry.services.ExpressionEvaluator
030     * @author Howard Lewis Ship
031     * @since 2.2
032     */
033    
034    public class ExpressionBinding extends AbstractBinding
035    {
036        /**
037         * The root object against which the nested property name is evaluated.
038         */
039    
040        private final IComponent _root;
041    
042        /**
043         * The OGNL expression, as a string.
044         */
045    
046        private String _expression;
047    
048        /**
049         * If true, then the binding is invariant.
050         */
051    
052        private boolean _invariant = false;
053    
054        /**
055         * Parsed OGNL expression.
056         */
057    
058        private Object _parsedExpression;
059    
060        /**
061         * Flag set to true once the binding has initialized.
062         */
063    
064        private boolean _initialized;
065    
066        /**
067         * @since 4.0
068         */
069    
070        private ExpressionEvaluator _evaluator;
071    
072        /** @since 4.0 */
073    
074        private ExpressionCache _cache;
075    
076        /**
077         * Creates a {@link ExpressionBinding}from the root object and an OGNL expression.
078         */
079    
080        public ExpressionBinding(String description, Location location, ValueConverter valueConverter,
081                IComponent root, String expression, ExpressionEvaluator evaluator,
082                ExpressionCache cache)
083        {
084            super(description, valueConverter, location);
085    
086            _root = root;
087            _expression = expression;
088            _evaluator = evaluator;
089            _cache = cache;
090        }
091    
092        /**
093         * Gets the value of the property path, with the assistance of the {@link ExpressionEvaluator}.
094         * 
095         * @throws BindingException
096         *             if an exception is thrown accessing the property.
097         */
098    
099        public Object getObject()
100        {
101            initialize();
102    
103            return resolveExpression();
104        }
105    
106        private Object resolveExpression()
107        {
108            try
109            {
110                return _evaluator.readCompiled(_root, _parsedExpression);
111            }
112            catch (Throwable t)
113            {
114                throw new BindingException(t.getMessage(), this, t);
115            }
116        }
117    
118        /**
119         * Returns true if the binding is expected to always return the same value.
120         */
121    
122        public boolean isInvariant()
123        {
124            initialize();
125    
126            return _invariant;
127        }
128    
129        /**
130         * Sets up the helper object, but also optimizes the property path and determines if the binding
131         * is invarant.
132         */
133    
134        private void initialize()
135        {
136            if (_initialized)
137                return;
138    
139            _initialized = true;
140    
141            try
142            {
143                _parsedExpression = _cache.getCompiledExpression(_expression);
144    
145                _invariant = _evaluator.isConstant(_expression);
146            }
147            catch (Exception ex)
148            {
149                throw new BindingException(ex.getMessage(), this, ex);
150            }
151        }
152    
153        /**
154         * Updates the property for the binding to the given value.
155         * 
156         * @throws BindingException
157         *             if the property can't be updated (typically due to an security problem, or a
158         *             missing mutator method).
159         * @throws ReadOnlyBindingException
160         *             if the binding is invariant.
161         */
162    
163        public void setObject(Object value)
164        {
165            initialize();
166    
167            if (_invariant)
168                throw createReadOnlyBindingException(this);
169    
170            try
171            {
172                _evaluator.writeCompiled(_root, _parsedExpression, value);
173            }
174            catch (Throwable ex)
175            {
176                throw new BindingException(ex.getMessage(), this, ex);
177            }
178        }
179    
180        /**
181         * Returns the a String representing the property path. This includes the
182         * {@link IComponent#getExtendedId() extended id}of the root component and the property path
183         * ... once the binding is used, these may change due to optimization of the property path.
184         */
185    
186        public String toString()
187        {
188            StringBuffer buffer = new StringBuffer();
189    
190            buffer.append("ExpressionBinding[");
191            buffer.append(_root.getExtendedId());
192    
193            if (_expression != null)
194            {
195                buffer.append(' ');
196                buffer.append(_expression);
197            }
198    
199            buffer.append(']');
200    
201            return buffer.toString();
202        }
203    
204        /** @since 4.0 */
205        public Object getComponent()
206        {
207            return _root;
208        }
209    }