001    package org.apache.ojb.broker.metadata.fieldaccess;
002    
003    /* Copyright 2003-2005 The Apache Software Foundation
004     *
005     * Licensed under the Apache License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    import java.beans.BeanInfo;
019    import java.beans.IntrospectionException;
020    import java.beans.Introspector;
021    import java.beans.PropertyDescriptor;
022    import java.lang.reflect.Method;
023    import java.util.ArrayList;
024    import java.util.List;
025    
026    import org.apache.commons.lang.StringUtils;
027    import org.apache.ojb.broker.core.proxy.ProxyHelper;
028    import org.apache.ojb.broker.metadata.MetadataException;
029    import org.apache.ojb.broker.util.ClassHelper;
030    import org.apache.ojb.broker.util.logging.Logger;
031    
032    /**
033     * A {@link PersistentField} implementation using
034     * JavaBeans compliant calls only to access persistent attributes.
035     * No Reflection is needed. But for each attribute xxx there must be
036     * public getXxx() and setXxx() methods. In metadata the field name must be
037     * the bean compliant 'xxx'.
038     *
039     * @version $Id: PersistentFieldIntrospectorImpl.java,v 1.1 2007-08-24 22:17:35 ewestfal Exp $
040     */
041    public class PersistentFieldIntrospectorImpl extends PersistentFieldBase
042    {
043        private static final long serialVersionUID = 8805309492150404444L;
044        private Class type;
045        private transient List propertyGraph;
046    
047        public PersistentFieldIntrospectorImpl()
048        {
049            super();
050        }
051    
052        public PersistentFieldIntrospectorImpl(Class aClass, String aPropertyName)
053        {
054            super(aClass, aPropertyName);
055        }
056    
057        public Class getType()
058        {
059            if (type == null)
060            {
061                type = getPropertyDescriptor().getPropertyType();
062            }
063            return type;
064        }
065    
066        public void set(Object target, Object value) throws MetadataException
067        {
068            if(target == null) return;
069            List propertyDescriptors = getPropertyGraph();
070            int size = propertyDescriptors.size() - 1;
071            PropertyDescriptor pd;
072            for (int i = 0; i < size; i++)
073            {
074                Object attribute;
075                pd = (PropertyDescriptor) propertyDescriptors.get(i);
076                attribute = getValueFrom(pd, target);
077                if (attribute != null || value != null)
078                {
079                    if (attribute == null)
080                    {
081                        try
082                        {
083                            attribute = ClassHelper.newInstance(pd.getPropertyType());
084                        }
085                        catch (Exception e)
086                        {
087                            throw new MetadataException("Can't instantiate nested object of type '"
088                                    + pd.getPropertyType() + "' for field '"
089                                    + pd.getName() + "'", e);
090                        }
091                    }
092                    setValueFor(pd, target, attribute);
093                }
094                else
095                {
096                    return;
097                }
098                target = attribute;
099            }
100            pd = (PropertyDescriptor) propertyDescriptors.get(size);
101            setValueFor(pd, target, value);
102        }
103    
104        public Object get(Object target) throws MetadataException
105        {
106            List propertyDescriptors = getPropertyGraph();
107            for (int i = 0; i < propertyDescriptors.size(); i++)
108            {
109                PropertyDescriptor pd = (PropertyDescriptor) propertyDescriptors.get(i);
110                target = getValueFrom(pd, target);
111                if (target == null) break;
112            }
113            return target;
114        }
115    
116        private Object getValueFrom(PropertyDescriptor pd, Object target)
117        {
118            if (target == null) return null;
119            Method m = pd.getReadMethod();
120            if (m != null)
121            {
122                try
123                {
124                    return m.invoke(ProxyHelper.getRealObject(target), null);
125                }
126                catch (Throwable e)
127                {
128                    logProblem(pd, target, null, "Can't read value from given object");
129                    throw new MetadataException("Error invoking method:" + m.getName() + " in object " + target.getClass().getName(), e);
130                }
131            }
132            else
133            {
134                throw new MetadataException("Can't get ReadMethod for property:" + pd.getName() + " in object " + target.getClass().getName());
135            }
136        }
137    
138        private void setValueFor(PropertyDescriptor pd, Object target, Object value)
139        {
140            Method m = pd.getWriteMethod();
141            Object[] args = {value};
142            if (m != null)
143            {
144                try
145                {
146                    /**
147                     * MBAIRD: it is safe to call getParameterTypes()[0] because this is
148                     * the "set" method and it needs to take one parameter only.
149                     * we need to be able to set values to null. We can only set something to null if
150                     * the type is not a primitive (assignable from Object).
151                     */
152                    if ((value != null) || !m.getParameterTypes()[0].isPrimitive())
153                    {
154                        m.invoke(ProxyHelper.getRealObject(target), args);
155                    }
156                }
157                catch (Throwable e)
158                {
159                    logProblem(pd, target, value, "Can't set value on given object.");
160                    throw new MetadataException("Error invoking method:" + m.getName() + " in object:" + target.getClass().getName(), e);
161                }
162            }
163            else
164            {
165                throw new MetadataException("Can't get WriteMethod for property:" + pd.getName() + " in object:" + target.getClass().getName());
166            }
167        }
168    
169        private List getPropertyGraph()
170        {
171            if (propertyGraph == null)
172            {
173                propertyGraph = buildPropertyGraph();
174            }
175            return propertyGraph;
176        }
177    
178        private List buildPropertyGraph()
179        {
180            List result = new ArrayList();
181            String[] fields = StringUtils.split(getName(), PATH_TOKEN);
182            PropertyDescriptor pd = null;
183            for (int i = 0; i < fields.length; i++)
184            {
185                String fieldName = fields[i];
186                if (pd == null)
187                {
188                    pd = findPropertyDescriptor(getDeclaringClass(), fieldName);
189                }
190                else
191                {
192                    pd = findPropertyDescriptor(pd.getPropertyType(), fieldName);
193                }
194                result.add(pd);
195            }
196            return result;
197        }
198    
199        /**
200         * Get the PropertyDescriptor for aClass and aPropertyName
201         */
202        protected static PropertyDescriptor findPropertyDescriptor(Class aClass, String aPropertyName)
203        {
204            BeanInfo info;
205            PropertyDescriptor[] pd;
206            PropertyDescriptor descriptor = null;
207    
208            try
209            {
210                info = Introspector.getBeanInfo(aClass);
211                pd = info.getPropertyDescriptors();
212                for (int i = 0; i < pd.length; i++)
213                {
214                    if (pd[i].getName().equals(aPropertyName))
215                    {
216                        descriptor = pd[i];
217                        break;
218                    }
219                }
220                if (descriptor == null)
221                {
222                    /*
223                                     * Daren Drummond:      Throw here so we are consistent
224                                     *                                      with PersistentFieldDefaultImpl.
225                                     */
226                    throw new MetadataException("Can't find property " + aPropertyName + " in " + aClass.getName());
227                }
228                return descriptor;
229            }
230            catch (IntrospectionException ex)
231            {
232                /*
233                             * Daren Drummond:      Throw here so we are consistent
234                             *                                      with PersistentFieldDefaultImpl.
235                             */
236                throw new MetadataException("Can't find property " + aPropertyName + " in " + aClass.getName(), ex);
237            }
238        }
239    
240        /**
241         * Returns the PropertyDescriptor.
242         *
243         * @return java.beans.PropertyDescriptor
244         */
245        protected PropertyDescriptor getPropertyDescriptor()
246        {
247            return (PropertyDescriptor) getPropertyGraph().get(getPropertyGraph().size() - 1);
248        }
249    
250        /**
251         * This implementation returns always 'false'.
252         */
253        public boolean makeAccessible()
254        {
255            return false;
256        }
257    
258        /**
259         * Always returns 'false'.
260         *
261         * @see PersistentField#usesAccessorsAndMutators
262         */
263        public boolean usesAccessorsAndMutators()
264        {
265            return true;
266        }
267    
268        /**
269         * Let's give the user some hints as to what could be wrong.
270         */
271        protected void logProblem(PropertyDescriptor pd, Object anObject, Object aValue, String msg)
272        {
273            Logger logger = getLog();
274            logger.error("Error in [PersistentFieldPropertyImpl], " + msg);
275            logger.error("Declaring class [" + getDeclaringClass().getName() + "]");
276            logger.error("Property Name [" + getName() + "]");
277            logger.error("Property Type [" + pd.getPropertyType().getName() + "]");
278    
279            if (anObject != null)
280            {
281                logger.error("anObject was class [" + anObject.getClass().getName() + "]");
282            }
283            else
284            {
285                logger.error("anObject was null");
286            }
287            if (aValue != null)
288            {
289                logger.error("aValue was class [" + aValue.getClass().getName() + "]");
290            }
291            else
292            {
293                logger.error("aValue was null");
294            }
295        }
296    }