001 /* ==================================================================== 002 * The Apache Software License, Version 1.1 003 * 004 * Copyright (c) 2003 The Apache Software Foundation. All rights 005 * reserved. 006 * 007 * Redistribution and use in source and binary forms, with or without 008 * modification, are permitted provided that the following conditions 009 * are met: 010 * 011 * 1. Redistributions of source code must retain the above copyright 012 * notice, this list of conditions and the following disclaimer. 013 * 014 * 2. Redistributions in binary form must reproduce the above copyright 015 * notice, this list of conditions and the following disclaimer in 016 * the documentation and/or other materials provided with the 017 * distribution. 018 * 019 * 3. The end-user documentation included with the redistribution, 020 * if any, must include the following acknowledgment: 021 * "This product includes software developed by the 022 * Apache Software Foundation (http://www.apache.org/)." 023 * Alternately, this acknowledgment may appear in the software itself, 024 * if and wherever such third-party acknowledgments normally appear. 025 * 026 * 4. The names "The Jakarta Project", "Commons", and "Apache Software 027 * Foundation" must not be used to endorse or promote products derived 028 * from this software without prior written permission. For written 029 * permission, please contact apache@apache.org. 030 * 031 * 5. Products derived from this software may not be called "Apache", 032 * nor may "Apache" appear in their name, without prior written 033 * permission of the Apache Software Foundation. 034 * 035 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 036 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 037 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 038 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR 039 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 040 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 041 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 042 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 043 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 044 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 045 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 046 * SUCH DAMAGE. 047 * ==================================================================== 048 * 049 * This software consists of voluntary contributions made by many 050 * individuals on behalf of the Apache Software Foundation. For more 051 * information on the Apache Software Foundation, please see 052 * <http://www.apache.org/>. 053 * 054 */ 055 package org.jpu.patterns.proxy; 056 057 import java.io.Serializable; 058 import java.lang.reflect.Method; 059 import java.util.HashMap; 060 import java.util.HashSet; 061 import java.util.Map; 062 import java.util.Set; 063 064 import org.apache.commons.lang.ClassUtils; 065 import org.apache.commons.lang.ObjectUtils; 066 import org.jpu.patterns.common.CopyException; 067 import org.jpu.patterns.common.CopyOptions; 068 import org.jpu.patterns.common.JPUBeanUtils; 069 import org.jpu.patterns.proxy.JPUInvocationHandler; 070 071 /** 072 * Change-tracking invocation handler intended for use in implementing JavaBean's that are referenced through one or 073 * more interfaces. Automatically provides implementations of simple getter and setter methods, reducing the burden associated 074 * with maintaining large numbers of such methods. 075 * <p> 076 * While simple getters and setters are implemented automatically, more complex methods ("business" methods, for instance) can be explicitly 077 * defined. When the caller calls a method on the interface, this class will first attempt to delegate the method call to "<code>this</code>". 078 * If no such method exists in <code>this.getClass()</code> or any of its superclasses, then it will determine if the method is a 079 * getter or setter and, if so, query or update an internal <code>Map</code> of attributes; if not it will cause a 080 * {@link org.jpu.patterns.proxy.UnhandledMethodCallException} (unchecked) to be thrown. 081 * <p> 082 * If you wish to provide your own implementation of a method, add it to your <code>ProxyBean</code> subclass (making sure it 083 * has the same signature as the version in the interface). The proxy instance can be obtained via {@link #getProxy()} and you are 084 * free to call whichever methods on it you wish through the interface. To illustrate, suppose I have a class <code>AccountImpl</code> and 085 * interface <code>Account</code> defined as follows: 086 * <p> 087 * <pre> 088 * <code> 089 * public interface Account { 090 * public String getId(); 091 * public void setId(String id); 092 * public double getBalance(); 093 * public void setBalance(double bal); 094 * public void deposit(double amt); 095 * } 096 * 097 * // ... 098 * 099 * public class AccountImpl extends ProxyBean { 100 * public void initializeProxy(Object newProxy) { 101 * super.initializeProxy(newProxy); 102 * Account acct = (Account)newProxy; 103 * acct.setBalance(0.0); 104 * } 105 * public void deposit(double amt) { 106 * Account acct = (Account)getProxy(); 107 * acct.setBalance( acct.getBalance() + amt ); 108 * } 109 * } 110 * </code> 111 * </pre> 112 * Then the "<code>id</code>" attribute will be maintained automatically (getters and setters implemented automatically) while 113 * calls to <code>deposit()</code> will be delegated to <code>AccountImpl</code>'s <code>deposit()</code> method. Note the 114 * initial balance is set in <code>initializeProxy()</code>; without this the balance would start out <code>null</code>. 115 * <p> 116 * The internal <code>Map</code> that holds the attributes is made accessible to users of <code>ProxyBean</code> both for query 117 * and update. This facilitates convenient and efficient access to all the object's attributes for wholesale copying and 118 * other sorts of manipulation. 119 * <p> 120 * This class can also maintain changed flags on each of the attributes. By default this feature is disabled but can be 121 * enabled by overriding {@link #trackChanges()} and having it return <code>true</code>. If changes are tracked, then 122 * the public methods {@link #getChangedAttributes()} and {@link #isAttributeChanged(String)} can be used to query the flags and 123 * {@link #setChangedFlag(String)}, {@link #clearChangedFlag(String)}, and {@link #clearChangedFlags()} to change them. If you wish for the subclass to be notified 124 * of any changes, you may override the {@link #valueChanged(JPUBeanUtils.MethodDescriptor, Object, Object)} subclass hook. If you want to change the definition of 125 * equality of objects (default is <code>org.apache.commons.lang.ObjectUtils.equals()</code>), you can override {@link #areValuesEqual(JPUBeanUtils.MethodDescriptor , Object , Object )}. 126 * <p> 127 * This class inherits from {@link JPUInvocationHandler}, so if you wish to add additional logic to the invocation 128 * handler, override {@link #doInvoke(Object, Method, Object[], Object[])} and add the following lines at the top your implementation: 129 * <p> 130 * <pre> 131 * <code> 132 * if ( super.doInvoke(proxy, method, args, result) ) { 133 * return true; 134 * } 135 * </code> 136 * </pre> 137 * Definitions of "getter" and "setter" used here are the same ones defined by the JavaBeans specification; that is, a method is considered a getter if it 138 * starts with "get" and takes zero parameters; or if it starts with "is", takes zero parameters, and returns <code>boolean</code> or <code>Boolean</code>; 139 * while a method is considered a setter if it starts with "set" and takes one parameter. 140 * <p> 141 * <code>ProxyBean</code> can be quite useful in J2EE environments that use the 142 * <a href="http://java.sun.com/blueprints/patterns/TransferObject.html">Transfer Object</a> pattern to pass data between 143 * tiers. The change-tracking feature can be used to determine which items have been altered by the presentation tier and 144 * hence require persisting to the database. The JPU toolkit provides a class called {@link org.jpu.patterns.transferObject.ProxyTransferObject} which 145 * is intended for this purpose; it extends <code>ProxyBean</code> and adds serializability to the 146 * invocation handler (which of course is necessary for passing such objects between tiers). 147 * <p> 148 */ 149 public class ProxyBean extends JPUInvocationHandler implements Serializable { 150 /** 151 * Returns a <code>Map</code> containing all the attributes. 152 * The map is modifiable and the caller is free to change it as required. 153 */ 154 public Map getAttributes() { 155 return _attributes; 156 } 157 158 /** 159 * Returns the value of the named attribute, or <code>null</code> 160 * if that attribute is not present. 161 */ 162 public Object getAttribute(String attName) { 163 return getAttributes().get(attName); 164 } 165 166 /** 167 * Returns the names of all attributes that have changed since 168 * the last call to {@link #clearChangedFlags()} (or since 169 * the object's creation). If {@link #trackChanges()} 170 * returns <code>false</code> (the default), the returned 171 * <code>Set</code> will be empty. 172 * <p> 173 * The returned <code>Set</code> is modifiable and the caller 174 * is free to change it as required. 175 */ 176 public Set getChangedAttributes() { 177 if ( _changedAttributes == null ) { 178 // changedAttributes flags are initialized lazily. 179 _changedAttributes = new HashSet(); 180 } 181 return _changedAttributes; 182 } 183 184 /** 185 * Convenience alias for "<code> ! getChangedAttributes().isEmpty()</code>". 186 */ 187 public boolean areAnyAttributesChanged() { 188 return ! getChangedAttributes().isEmpty(); 189 } 190 191 /** 192 * Convenience alias for "<code>getChangedAttributes().contains(attributeName)</code>". 193 */ 194 public boolean isAttributeChanged(String attributeName) { 195 return getChangedAttributes().contains(attributeName); 196 } 197 198 /** 199 * Convenience alias for "<code>copyChangesTo(dest, null)</code>". 200 */ 201 public Map copyChangesTo(Object dest) throws CopyException { 202 return copyChangesTo(dest, null); 203 } 204 205 /** 206 * Copies all attributes from this object to <code>dest</code> using the given options. 207 * The implementation uses {@link JPUBeanUtils#copy}. 208 */ 209 public Map copyChangesTo(Object dest, CopyOptions options) throws CopyException { 210 options = CopyOptions.defaultIfNull(options); 211 if ( options.sourceClasses == null ) { 212 options.setSourceClasses( (Class[])ClassUtils.getAllInterfaces( getProxy().getClass() ).toArray( new Class[0] ) ); 213 } 214 options.setAttributesToCopy( getChangedAttributes() ); 215 return JPUBeanUtils.copy(getProxy(), dest, options); 216 } 217 218 /** 219 * Convenience alias for "<code>copyTo(dest,null)</code>". 220 */ 221 public Map copyTo(Object dest) throws CopyException { 222 return copyTo(dest, null); 223 } 224 225 /** 226 * Copies all attributes to "<code>dest</code>" (which needn't be backed by a <code>ProxyBean</code>) 227 * with the given {@link org.jpu.patterns.common.CopyOptions} using {@link org.jpu.patterns.common.JPUBeanUtils#copy}. 228 * If "<code>options.sourceClasses</code>" is <code>null</code>, it is populated with an array containing all 229 * interfaces implemented by the proxy prior to the call to {@link org.jpu.patterns.common.JPUBeanUtils#copy}. 230 */ 231 public Map copyTo(Object dest, CopyOptions options) throws CopyException { 232 options = CopyOptions.defaultIfNull(options); 233 if ( options.sourceClasses == null ) { 234 options.setSourceClasses( (Class[])ClassUtils.getAllInterfaces( getProxy().getClass() ).toArray( new Class[0] ) ); 235 } 236 return JPUBeanUtils.copy(getProxy(), dest, options); 237 } 238 239 /** 240 * Sets the changed flag for the named attribute. 241 */ 242 public void setChangedFlag(String attributeName) { 243 getChangedAttributes().add(attributeName); 244 } 245 246 /** 247 * Clears the changed flag for the named attribute. 248 */ 249 public void clearChangedFlag(String attributeName) { 250 getChangedAttributes().remove(attributeName); 251 } 252 253 /** 254 * Clears all changed flas. 255 */ 256 public void clearChangedFlags() { 257 getChangedAttributes().clear(); 258 } 259 260 /** 261 * Overrides the corresponding method of {@link org.jpu.patterns.proxy.JPUInvocationHandler}. 262 * Calls <code>super.doInvoke()</code> and, if its return value is <code>false</code>, calls 263 * {@link #autoDelegateToMap()}. If that method returns <code>true</code>, calls 264 * {@link #delegateToMap(Object,Method,Object[],Object[])} and returns the result; else returns <code>false</code>. 265 */ 266 protected boolean doInvoke(Object proxy, Method method, Object[] args, Object[] result) throws Throwable { 267 if ( super.doInvoke(proxy, method, args, result) ) { 268 return true; 269 } 270 return autoDelegateToMap() ? delegateToMap(proxy, method, args, result) : false; 271 } 272 273 /** 274 * Returns whether implementations of getters and setters are automatically provided. The default implementation 275 * returns <code>true</code>, but subclasses are free to override. If <code>false</code> is returned, the 276 * subclass may still invoke the "Map delegation" feature by calling {@link #delegateToMap(Object,Method,Object[],Object[])}. 277 */ 278 protected boolean autoDelegateToMap() { 279 return true; 280 } 281 282 /** 283 * Returns whether changes are being tracked. Default is <code>false</code>, but subclasses are free to override. 284 */ 285 public boolean trackChanges() { 286 return false; 287 } 288 289 /** 290 * Convenience alias for "<code>delegateToMap(proxy, null, args, result)</code>". 291 */ 292 protected boolean delegateToMap(Object proxy, Object[] args, Object[] result) throws Throwable { 293 return delegateToMap(proxy, null, args, result); 294 } 295 296 protected boolean delegateToMap(Object proxy, Method method, Object[] args, Object[] result) throws Throwable { 297 if ( method == null ) { 298 method = getCurrentInterfaceMethod(); 299 } 300 boolean handled = false; 301 JPUBeanUtils.MethodDescriptor desc = getDescriptor(method); 302 if ( desc.type == desc.GETTER ) { 303 result[0] = getAttribute( desc ); 304 handled = true; 305 } 306 else if ( desc.type == desc.SETTER ) { 307 setAttribute(desc, args); 308 handled = true; 309 } 310 return handled; 311 } 312 313 protected JPUBeanUtils.MethodDescriptor getDescriptor(Method method) { 314 return JPUBeanUtils.getDescriptor(method); 315 } 316 317 protected void setAttribute(JPUBeanUtils.MethodDescriptor method, Object[] args) throws Throwable { 318 319 Object oldValue = getAttributes().put(method.attributeName, args[0]); 320 if ( trackChanges() ) { 321 Object newValue = args[0]; 322 if ( ! areValuesEqual(method, oldValue, newValue) ) { 323 valueChanged(method, oldValue, newValue); 324 setChangedFlag( method.attributeName ); 325 } 326 } 327 } 328 329 /** 330 * Hook that is invoked if change tracking is enabled and the value of an attribute has changed as a 331 * result of a setter invocation. The old and new values are passed, along with the invoked 332 * setter's {@link org.jpu.patterns.common.JPUBeanUtils.MethodDescriptor}. 333 */ 334 protected void valueChanged(JPUBeanUtils.MethodDescriptor method, Object oldValue, Object newValue) {} 335 336 /** 337 * Returns whether the two given values are considered equal. "<code>method</code>" is the {@link org.jpu.patterns.common.JPUBeanUtils.MethodDescriptor} 338 * of the invoked setter. 339 */ 340 protected boolean areValuesEqual(JPUBeanUtils.MethodDescriptor method, Object oldValue, Object newValue) { 341 return ObjectUtils.equals(oldValue, newValue); 342 } 343 344 /** 345 * Returns the value of the attribute associated with the given getter method. The default 346 * implementation simply returns "<code>getAttributes().get(method.attributeName)</code>", but 347 * subclasses are free to override. 348 */ 349 protected Object getAttribute(JPUBeanUtils.MethodDescriptor method) throws Throwable { 350 return getAttributes().get(method.attributeName); 351 } 352 353 private Map _attributes = new HashMap(); 354 private Set _changedAttributes = null; 355 } 356