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