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 }