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 }