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 }