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