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 }