001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016// begin Kuali Foundation modification
017package org.kuali.rice.kns.web.struts.form.pojo;
018
019import org.apache.commons.beanutils.DynaBean;
020import org.apache.commons.beanutils.DynaProperty;
021import org.apache.commons.beanutils.MappedPropertyDescriptor;
022import org.apache.commons.beanutils.MethodUtils;
023import org.apache.commons.beanutils.NestedNullException;
024import org.apache.commons.beanutils.PropertyUtils;
025import org.apache.commons.beanutils.PropertyUtilsBean;
026import org.apache.commons.beanutils.WrapDynaBean;
027import org.apache.commons.beanutils.expression.Resolver;
028import org.apache.commons.collections.FastHashMap;
029import org.apache.log4j.Logger;
030import org.kuali.rice.core.web.format.Formatter;
031import org.kuali.rice.kns.service.KNSServiceLocator;
032import org.kuali.rice.krad.bo.PersistableBusinessObject;
033import org.kuali.rice.krad.bo.PersistableBusinessObjectBaseAdapter;
034import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
035import org.kuali.rice.krad.service.LegacyDataAdapter;
036import org.kuali.rice.krad.service.PersistenceStructureService;
037import org.kuali.rice.krad.util.ObjectUtils;
038
039import java.beans.IndexedPropertyDescriptor;
040import java.beans.IntrospectionException;
041import java.beans.Introspector;
042import java.beans.PropertyDescriptor;
043import java.lang.reflect.InvocationTargetException;
044import java.lang.reflect.Method;
045import java.util.ArrayList;
046import java.util.Collection;
047import java.util.HashMap;
048import java.util.List;
049import java.util.Map;
050
051/**
052 * begin Kuali Foundation modification
053 * This class is used to access the properties of a Pojo bean.
054 *
055 * @deprecated KNS Struts deprecated, use KRAD and the Spring MVC framework.
056 * deleted author tag
057 * end Kuali Foundation modification
058 */
059// Kuali Foundation modification: class originally SLPropertyUtilsBean
060@Deprecated
061public class PojoPropertyUtilsBean extends PropertyUtilsBean {
062
063    public static final Logger LOG = Logger.getLogger(PojoPropertyUtilsBean.class.getName());
064
065    /**
066     * Thin interface for determining the appropriate item class for a collection property
067     */
068    public static interface CollectionItemClassProvider {
069        public Class getCollectionItemClass(Object bean, String property);
070    }
071
072    /**
073     * CollectionItemClassProvider backed by the legacy data adapter
074     */
075    public static class LegacyDataAdapterProvider implements CollectionItemClassProvider {
076        protected static LegacyDataAdapter legacyDataAdapter = null;
077
078        protected static LegacyDataAdapter getLegacyDataAdapter() {
079            if (legacyDataAdapter == null) {
080                legacyDataAdapter = KRADServiceLocatorWeb.getLegacyDataAdapter();
081            }
082
083            return legacyDataAdapter;
084        }
085
086        @Override
087        public Class getCollectionItemClass(Object bean, String property) {
088            Map<String, Class> collectionObjectTypes = getLegacyDataAdapter().listCollectionObjectTypes(bean.getClass());
089
090            return collectionObjectTypes.get(property);
091        }
092    }
093
094    /**
095     * CollectionItemClassProvider backed by OJB metadata
096     */
097    public static class PersistenceStructureServiceProvider implements CollectionItemClassProvider {
098        protected static PersistenceStructureService persistenceStructureService = null;
099        protected static PersistenceStructureService getPersistenceStructureService() {
100            if (persistenceStructureService == null) {
101                persistenceStructureService = KNSServiceLocator.getPersistenceStructureService();
102            }
103            return persistenceStructureService;
104        }
105
106        @Override
107        public Class getCollectionItemClass(Object bean, String property) {
108            Map<String, Class> collectionObjectTypes = getPersistenceStructureService().listCollectionObjectTypes(bean.getClass());
109            return collectionObjectTypes.get(property);
110        }
111    }
112
113    // default is to consult LegacyDataAdapter
114    protected static CollectionItemClassProvider collectionItemClassProvider = new LegacyDataAdapterProvider();
115
116        // begin Kuali Foundation modification
117    public PojoPropertyUtilsBean() {
118        super();
119    }
120    // end Kuali Foundation modification
121
122    public Object getProperty(Object bean, String key) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
123        // begin Kuali Foundation modification
124        if (!(bean instanceof PojoForm))
125            return super.getProperty(bean, key);
126
127        PojoForm form = (PojoForm) bean;
128        Map unconvertedValues = form.getUnconvertedValues();
129
130        if (unconvertedValues.containsKey(key))
131            return unconvertedValues.get(key);
132
133        Object val = getNestedProperty(bean, key);
134        Class type = (val!=null)?val.getClass():null;
135        if ( type == null ) {
136            try {
137                type = getPropertyType(bean, key);
138            } catch ( Exception ex ) {
139                type = String.class;
140                LOG.warn( "Unable to get property type for Class: " + bean.getClass().getName() + "/Property: " + key );
141            }
142        }
143        return (Formatter.isSupportedType(type) ? form.formatValue(val, key, type) : val);
144        // end Kuali Foundation modification
145    }
146
147        // begin Kuali Foundation modification
148    private Map<String,List<Method>> cache = new HashMap<String, List<Method>>();
149    private static Map<String,Method> readMethodCache = new HashMap<String, Method>();
150    private IntrospectionException introspectionException = new IntrospectionException( "" );
151
152    public Object fastGetNestedProperty(Object obj, String propertyName) throws IntrospectionException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
153        //logger.debug("entering fastGetNestedProperty");
154
155        List<Method> methods = (List<Method>) cache.get(propertyName + obj.getClass().getName());
156        if (methods == null) {
157            methods = new ArrayList<Method>();
158            Object currentObj = obj;
159            Class<?> currentObjClass = currentObj.getClass();
160
161            for (String currentPropertyName : propertyName.split("\\.") ) {
162                String cacheKey = currentObjClass.getName() + currentPropertyName;
163                Method readMethod = readMethodCache.get( cacheKey );
164                if ( readMethod == null ) {
165                        synchronized (readMethodCache) {
166                            // if the read method was resolved to an error, repeat the exception
167                            // rather than performing the reflection calls below
168                            if ( readMethodCache.containsKey(cacheKey) ) {
169                                throw introspectionException;
170                            }
171                            try {
172                                try {
173                                    readMethod = currentObjClass.getMethod("get" + currentPropertyName.substring(0, 1).toUpperCase() + currentPropertyName.substring(1), (Class[])null);
174                                } catch (NoSuchMethodException e) {
175                                    readMethod = currentObjClass.getMethod("is" + currentPropertyName.substring(0, 1).toUpperCase() + currentPropertyName.substring(1), (Class[])null);
176                                }
177                            } catch ( NoSuchMethodException ex ) {
178                                // cache failures to prevent re-checking of the parameter
179                                readMethodCache.put( cacheKey, null );
180                                throw introspectionException;
181                            }
182                            readMethodCache.put(cacheKey, readMethod );
183                                        }
184                }
185                methods.add(readMethod);
186                currentObjClass = readMethod.getReturnType();
187            }
188            synchronized (cache) {
189                cache.put(propertyName + obj.getClass().getName(), methods);
190                        }
191        }
192
193        for ( Method method : methods ) {
194            obj = method.invoke(obj, (Object[])null);
195        }
196
197        //logger.debug("exiting fastGetNestedProperty");
198
199        return obj;
200    }
201        // end Kuali Foundation modification
202
203    /*
204     *  Kuali modification to make isWriteable work like it did in beanUtils 1.7.
205     *  Checking for nested nulls caused exceptions in rice 2.0.
206     */
207    @Override
208    public boolean isWriteable(Object bean, String name) {
209        // Validate method parameters
210        if (bean == null) {
211            throw new IllegalArgumentException("No bean specified");
212        }
213        if (name == null) {
214            throw new IllegalArgumentException("No name specified for bean class '" +
215                    bean.getClass() + "'");
216        }
217
218       // Begin Kuali foundation modification
219
220        Resolver nestedResolver = getResolver();
221
222        // Resolve nested references
223        while (nestedResolver.hasNested(name)) {
224            String next = nestedResolver.next(name);
225            Object nestedBean = null;
226
227            try {
228                nestedBean = getProperty(bean, next);
229
230                // If an object on which we're trying to set a value is null,
231                // 1: get its type
232                // 2: if it's not an interface, create an instance of it and set the property
233                // 2a: if it is an interface, we can't instantiate it, so the property shouldn't be writeable.
234                if (nestedBean == null) {
235
236                    Class propertyType = getPropertyType(bean, next);
237                    if (propertyType != null && !propertyType.isInterface()) {
238                        Object newInstance = ObjectUtils.createNewObjectFromClass(propertyType);
239                        setSimpleProperty(bean, next, newInstance);
240                        nestedBean = getSimpleProperty(bean, next);
241                    } else {
242                        return false;
243                    }
244                }
245            } catch (IllegalAccessException e) {
246                return false;
247            } catch (InvocationTargetException e) {
248                return false;
249            } catch (NoSuchMethodException e) {
250                return false;
251            }
252
253            bean = nestedBean;
254            name = nestedResolver.remove(name);
255        }
256
257        // End Kuali foundation modification
258
259        // Remove any subscript from the final name value
260        name = getResolver().getProperty(name);
261
262        // Treat WrapDynaBean as special case - may be a read-only property
263        // (see Jira issue# BEANUTILS-61)
264        if (bean instanceof WrapDynaBean) {
265            bean = ((WrapDynaBean)bean).getInstance();
266        }
267
268        // Return the requested result
269        if (bean instanceof DynaBean) {
270            // All DynaBean properties are writeable
271            return (((DynaBean) bean).getDynaClass().getDynaProperty(name) != null);
272        } else {
273            try {
274                PropertyDescriptor desc =
275                        getPropertyDescriptor(bean, name);
276                if (desc != null) {
277                    Method writeMethod = desc.getWriteMethod();
278                    if (writeMethod == null) {
279                        if (desc instanceof IndexedPropertyDescriptor) {
280                            writeMethod = ((IndexedPropertyDescriptor) desc).getIndexedWriteMethod();
281                        } else if (desc instanceof MappedPropertyDescriptor) {
282                            writeMethod = ((MappedPropertyDescriptor) desc).getMappedWriteMethod();
283                        }
284                        writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod);
285                    }
286                    return (writeMethod != null);
287                } else {
288                    return (false);
289                }
290            } catch (IllegalAccessException e) {
291                return (false);
292            } catch (InvocationTargetException e) {
293                return (false);
294            } catch (NoSuchMethodException e) {
295                return (false);
296            }
297        }
298
299    }
300
301    /**
302     * begin Kuali Foundation modification
303     * removed comments and @<no space>since javadoc attribute
304     * end Kuali Foundation modification
305     * @see org.apache.commons.beanutils.PropertyUtilsBean#getNestedProperty(java.lang.Object, java.lang.String)
306     */
307    public Object getNestedProperty(Object arg0, String arg1) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
308                // begin Kuali Foundation modification
309        try {
310            try {
311                return fastGetNestedProperty(arg0, arg1);
312            }
313            catch (Exception e) {
314                return super.getNestedProperty(arg0, arg1);
315            }
316        }
317        catch (NestedNullException e) {
318            return getUnreachableNestedProperty(arg0, arg1);
319        }
320        catch (InvocationTargetException e1) {
321            return getUnreachableNestedProperty(arg0, arg1);
322        }
323        // removed commented code
324        // end Kuali Foundation modification
325    }
326
327    /**
328     * Customization of superclass getNestedProperty which transparently creates indexed property items
329     * {@inheritDoc}
330     */
331    public Object getIndexedProperty(Object bean, String name, int index) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
332        try {
333            return super.getIndexedProperty(bean, name, index);
334        } catch (IndexOutOfBoundsException ioobe) {
335            return generateIndexedProperty(bean, name, index, ioobe);
336        }
337    }
338
339    protected Object generateIndexedProperty(Object nestedBean, String property, int index,
340            IndexOutOfBoundsException ioobe) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
341
342        if (!(nestedBean instanceof PersistableBusinessObject)
343                && !(nestedBean instanceof PersistableBusinessObjectBaseAdapter)) {
344            throw ioobe;
345        }
346
347        // we can only grow lists
348        if (!List.class.isAssignableFrom(getPropertyType(nestedBean, property))) {
349            throw ioobe;
350        }
351
352        List list = (List) getProperty(nestedBean, property);
353
354        Class c = collectionItemClassProvider.getCollectionItemClass(nestedBean, property);
355
356        if (c == null) {
357            throw new RuntimeException(
358                    "Unable to determined item class for collection '" + property + "' on bean of type '" + nestedBean
359                            .getClass() + "'");
360        }
361
362        Object value;
363        try {
364            value = c.newInstance();
365        } catch (InstantiationException ie) {
366            throw new RuntimeException("Error instantiating item class: " + c);
367        }
368
369        // fill any missing indices
370        while (list.size() <= index) {
371            list.add(null);
372        }
373        list.set(index, value);
374
375        return super.getIndexedProperty(nestedBean, property, index);
376    }
377
378    // begin Kuali Foundation modification
379    /**
380     * helper method makes sure we don't return "" for collections
381     */
382    private Object getUnreachableNestedProperty(Object arg0, String arg1) {
383        try {
384            PropertyDescriptor propertyDescriptor  = getPropertyDescriptor(arg0, arg1);
385            if (propertyDescriptor == null || Collection.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
386                return null;
387            }
388        } catch (IllegalAccessException e) {
389            // ignore
390        } catch (InvocationTargetException e) {
391            // ignore
392        } catch (NoSuchMethodException e) {
393            // ignore
394        }
395
396        return "";
397    }
398    // end Kuali Foundation modification
399
400
401    // begin Kuali Foundation modification
402    /**
403     * begin Kuali Foundation modification
404     * Set the value of the (possibly nested) property of the specified name, for the specified bean, with no type conversions.
405     *
406     * @param bean Bean whose property is to be modified
407     * @param name Possibly nested name of the property to be modified
408     * @param value Value to which the property is to be set
409     *
410     * @exception IllegalAccessException if the caller does not have access to the property accessor method
411     * @exception IllegalArgumentException if <code>bean</code> or <code>name</code> is null
412     * @exception IllegalArgumentException if a nested reference to a property returns null
413     * @exception InvocationTargetException if the property accessor method throws an exception
414     * @exception NoSuchMethodException if an accessor method for this propety cannot be found
415     * end Kuali Foundation modification
416     */
417    public void setNestedProperty(Object bean, String name, Object value) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
418
419        if (bean == null) {
420                if (LOG.isDebugEnabled()) LOG.debug("No bean specified, name = " + name + ", value = " + value);
421                return;
422        }
423        if (name == null) {
424            throw new IllegalArgumentException("No name specified");
425        }
426
427        Object propBean = null;
428        int indexOfINDEXED_DELIM = -1;
429        int indexOfMAPPED_DELIM = -1;
430        while (true) {
431            int delim = name.indexOf(PropertyUtils.NESTED_DELIM);
432            if (delim < 0) {
433                break;
434            }
435            String next = name.substring(0, delim);
436            indexOfINDEXED_DELIM = next.indexOf(PropertyUtils.INDEXED_DELIM);
437            indexOfMAPPED_DELIM = next.indexOf(PropertyUtils.MAPPED_DELIM);
438            if (bean instanceof Map) {
439                propBean = ((Map) bean).get(next);
440            }
441            else if (indexOfMAPPED_DELIM >= 0) {
442                propBean = getMappedProperty(bean, next);
443            }
444            else if (indexOfINDEXED_DELIM >= 0) {
445                propBean = getIndexedProperty(bean, next);
446            }
447            else {
448                propBean = getSimpleProperty(bean, next);
449            }
450            if (ObjectUtils.isNull(propBean)) {
451                Class propertyType = getPropertyType(bean, next);
452                if (propertyType != null) {
453                        Object newInstance = ObjectUtils.createNewObjectFromClass(propertyType);
454                    setSimpleProperty(bean, next, newInstance);
455                    propBean = getSimpleProperty(bean, next);
456                }
457            }
458            bean = propBean;
459            name = name.substring(delim + 1);
460        }
461
462        indexOfINDEXED_DELIM = name.indexOf(PropertyUtils.INDEXED_DELIM);
463        indexOfMAPPED_DELIM = name.indexOf(PropertyUtils.MAPPED_DELIM);
464
465        if (bean instanceof Map) {
466            // check to see if the class has a standard property
467            PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
468            if (descriptor == null) {
469                // no - then put the value into the map
470                ((Map) bean).put(name, value);
471            }
472            else {
473                // yes - use that instead
474                setSimpleProperty(bean, name, value);
475            }
476        }
477        else if (indexOfMAPPED_DELIM >= 0) {
478            setMappedProperty(bean, name, value);
479        }
480        else if (indexOfINDEXED_DELIM >= 0) {
481            setIndexedProperty(bean, name, value);
482        }
483        else {
484            setSimpleProperty(bean, name, value);
485        }
486    }
487    // end Kuali Foundation modification
488
489        // begin Kuali Foundation modification
490    /**
491     * <p>
492     * Retrieve the property descriptor for the specified property of the specified bean, or return <code>null</code> if there is
493     * no such descriptor. This method resolves indexed and nested property references in the same manner as other methods in this
494     * class, except that if the last (or only) name element is indexed, the descriptor for the last resolved property itself is
495     * returned.
496     * </p>
497     *
498     * <p>
499     * <strong>FIXME </strong>- Does not work with DynaBeans.
500     * </p>
501     *
502     * @param bean Bean for which a property descriptor is requested
503     * @param name Possibly indexed and/or nested name of the property for which a property descriptor is requested
504     *
505     * @exception IllegalAccessException if the caller does not have access to the property accessor method
506     * @exception IllegalArgumentException if <code>bean</code> or <code>name</code> is null
507     * @exception IllegalArgumentException if a nested reference to a property returns null
508     * @exception InvocationTargetException if the property accessor method throws an exception
509     * @exception NoSuchMethodException if an accessor method for this propety cannot be found
510     */
511    public PropertyDescriptor getPropertyDescriptor(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
512        if (bean == null) {
513                if (LOG.isDebugEnabled()) LOG.debug("No bean specified, name = " + name);
514                return null;
515        }
516        if (name == null) {
517            throw new IllegalArgumentException("No name specified");
518        }
519        try {
520            // Resolve nested references
521            Object propBean = null;
522
523            // Begin Kuali foundation modification
524            int propertyTypeRetryAttempts = 0;
525            // End Kuali foundation modification
526
527            while (true) {
528                int delim = findNextNestedIndex(name);
529                //int delim = name.indexOf(PropertyUtils.NESTED_DELIM);
530                if (delim < 0) {
531                    break;
532                }
533                String next = name.substring(0, delim);
534                int indexOfINDEXED_DELIM = next.indexOf(PropertyUtils.INDEXED_DELIM);
535                int indexOfMAPPED_DELIM = next.indexOf(PropertyUtils.MAPPED_DELIM);
536                if (indexOfMAPPED_DELIM >= 0 && (indexOfINDEXED_DELIM < 0 || indexOfMAPPED_DELIM < indexOfINDEXED_DELIM)) {
537                    propBean = getMappedProperty(bean, next);
538                }
539                else {
540                    if (indexOfINDEXED_DELIM >= 0) {
541                        propBean = getIndexedProperty(bean, next);
542                    }
543                    else {
544                        propBean = getSimpleProperty(bean, next);
545                    }
546                }
547                if (ObjectUtils.isNull(propBean)) {
548                    Class propertyType = getPropertyType(bean, next);
549
550                    // Begin Kuali foundation modification
551                    if (propertyType != null && !propertyType.isInterface()) {
552                        Object newInstance = ObjectUtils.createNewObjectFromClass(propertyType);
553                        setSimpleProperty(bean, next, newInstance);
554                        propBean = getSimpleProperty(bean, next);
555                    } else if (propertyType != null && propertyType.isInterface()) {
556                        Introspector.flushFromCaches(bean.getClass());
557                        clearDescriptors();
558
559                        if (propertyTypeRetryAttempts++ >= 5) {
560                            throw new RuntimeException("Unable to determine real type of " + next + " of type " + bean.getClass() + "  after " + propertyTypeRetryAttempts + " attempts");
561                        }
562
563                        continue;
564                    }
565                    // End Kuali foundation modification
566
567                    if (propertyType != null) {
568                        Object newInstance = ObjectUtils.createNewObjectFromClass(propertyType);
569                        setSimpleProperty(bean, next, newInstance);
570                        propBean = getSimpleProperty(bean, next);
571                    }
572                }
573                bean = propBean;
574                name = name.substring(delim + 1);
575            }
576
577            // Remove any subscript from the final name value
578            int left = name.indexOf(PropertyUtils.INDEXED_DELIM);
579            if (left >= 0) {
580                name = name.substring(0, left);
581            }
582            left = name.indexOf(PropertyUtils.MAPPED_DELIM);
583            if (left >= 0) {
584                name = name.substring(0, left);
585            }
586
587            // Look up and return this property from our cache
588            // creating and adding it to the cache if not found.
589            if ((bean == null) || (name == null)) {
590                return (null);
591            }
592
593            PropertyDescriptor descriptors[] = getPropertyDescriptors(bean);
594            if (descriptors != null) {
595
596                for (int i = 0; i < descriptors.length; i++) {
597                    if (name.equals(descriptors[i].getName()))
598                        return (descriptors[i]);
599                }
600            }
601
602            PropertyDescriptor result = null;
603            FastHashMap mappedDescriptors = getMappedPropertyDescriptors(bean);
604            if (mappedDescriptors == null) {
605                mappedDescriptors = new FastHashMap();
606                mappedDescriptors.setFast(true);
607            }
608            result = (PropertyDescriptor) mappedDescriptors.get(name);
609            if (result == null) {
610                // not found, try to create it
611                try {
612                    result = new MappedPropertyDescriptor(name, bean.getClass());
613                }
614                catch (IntrospectionException ie) {
615                }
616                if (result != null) {
617                    mappedDescriptors.put(name, result);
618                }
619            }
620
621            return result;
622        } catch ( RuntimeException ex ) {
623            LOG.error( "Unable to get property descriptor for " + bean.getClass().getName() + " . " + name
624                    + "\n" + ex.getClass().getName() + ": " + ex.getMessage() );
625            throw ex;
626        }
627    }
628    // end Kuali Foundation modification
629
630    private int findNextNestedIndex(String expression)
631    {
632        // walk back from the end to the start
633        // and find the first index that
634        int bracketCount = 0;
635        for (int i=0, size=expression.length(); i<size ; i++) {
636            char at = expression.charAt(i);
637            switch (at) {
638                case PropertyUtils.NESTED_DELIM:
639                    if (bracketCount < 1) {
640                        return i;
641                    }
642                    break;
643
644                case PropertyUtils.MAPPED_DELIM:
645                case PropertyUtils.INDEXED_DELIM:
646                    // not bothered which
647                    ++bracketCount;
648                    break;
649
650                case PropertyUtils.MAPPED_DELIM2:
651                case PropertyUtils.INDEXED_DELIM2:
652                    // not bothered which
653                    --bracketCount;
654                    break;
655            }
656        }
657        // can't find any
658        return -1;
659    }
660
661    /**
662     * Set the value of the specified simple property of the specified bean,
663     * with no type conversions.
664     *
665     * @param bean Bean whose property is to be modified
666     * @param name Name of the property to be modified
667     * @param value Value to which the property should be set
668     *
669     * @exception IllegalAccessException if the caller does not have
670     *  access to the property accessor method
671     * @exception IllegalArgumentException if <code>bean</code> or
672     *  <code>name</code> is null
673     * @exception IllegalArgumentException if the property name is
674     *  nested or indexed
675     * @exception InvocationTargetException if the property accessor method
676     *  throws an exception
677     * @exception NoSuchMethodException if an accessor method for this
678     *  propety cannot be found
679     */
680    public void setSimpleProperty(Object bean,
681                                         String name, Object value)
682            throws IllegalAccessException, InvocationTargetException,
683            NoSuchMethodException {
684
685        if (bean == null) {
686                if (LOG.isDebugEnabled()) LOG.debug("No bean specified, name = " + name + ", value = " + value);
687                return;
688        }
689        if (name == null) {
690            throw new IllegalArgumentException("No name specified");
691        }
692
693        // Validate the syntax of the property name
694        if (name.indexOf(PropertyUtils.NESTED_DELIM) >= 0) {
695            throw new IllegalArgumentException
696                    ("Nested property names are not allowed");
697        } else if (name.indexOf(PropertyUtils.INDEXED_DELIM) >= 0) {
698            throw new IllegalArgumentException
699                    ("Indexed property names are not allowed");
700        } else if (name.indexOf(PropertyUtils.MAPPED_DELIM) >= 0) {
701            throw new IllegalArgumentException
702                    ("Mapped property names are not allowed");
703        }
704
705        // Retrieve the property setter method for the specified property
706        PropertyDescriptor descriptor =
707                getPropertyDescriptor(bean, name);
708        if (descriptor == null) {
709            throw new NoSuchMethodException("Unknown property '" +
710                    name + "'");
711        }
712        Method writeMethod = getWriteMethod(descriptor);
713        if (writeMethod == null) {
714            //throw new NoSuchMethodException("Property '" + name + "' has no setter method");
715                LOG.warn("Bean: " + bean.getClass().getName() + ", Property '" + name + "' has no setter method");
716                return;
717        }
718
719        // Call the property setter method
720        Object values[] = new Object[1];
721        values[0] = value;
722        if (LOG.isDebugEnabled()) {
723            String valueClassName =
724                value == null ? "<null>" : value.getClass().getName();
725            LOG.debug("setSimpleProperty: Invoking method " + writeMethod
726                      + " with value " + value + " (class " + valueClassName + ")");
727        }
728
729
730        invokeMethod(writeMethod, bean, values);
731
732    }
733
734    /** This just catches and wraps IllegalArgumentException. */
735    private Object invokeMethod(
736                        Method method,
737                        Object bean,
738                        Object[] values)
739                            throws
740                                IllegalAccessException,
741                                InvocationTargetException {
742        try {
743
744            return method.invoke(bean, values);
745
746        } catch (IllegalArgumentException e) {
747
748            LOG.error("Method invocation failed.", e);
749            throw new IllegalArgumentException(
750                "Cannot invoke " + method.getDeclaringClass().getName() + "."
751                + method.getName() + " - " + e.getMessage());
752
753        }
754    }
755
756    public Class getPropertyType(Object bean, String name)
757            throws IllegalAccessException, InvocationTargetException,
758            NoSuchMethodException {
759
760        if (bean == null) {
761            throw new IllegalArgumentException("No bean specified");
762        }
763        if (name == null) {
764            throw new IllegalArgumentException("No name specified for bean class '" +
765                    bean.getClass() + "'");
766        }
767
768        // Resolve nested references
769        while (getResolver().hasNested(name)) {
770            String next = getResolver().next(name);
771            Object nestedBean = getProperty(bean, next);
772            if (nestedBean == null) {
773                Class<?>[] paramTypes = {};
774                Method method = null;
775                try {
776                    method = bean.getClass().getMethod("get" + next.substring(0, 1).toUpperCase() + next.substring(1), (Class[])null);
777                } catch (NoSuchMethodException e) {
778                    method = bean.getClass().getMethod("is" + next.substring(0, 1).toUpperCase() + next.substring(1), (Class[])null);
779                }
780                try {
781                    nestedBean = ObjectUtils.createNewObjectFromClass(method.getReturnType());
782                                } catch (RuntimeException e) {
783                                        NestedNullException nne = new NestedNullException
784                    ("Null property value for '" + next +
785                    "' on bean class '" + bean.getClass() + "'");
786                    nne.initCause(e);
787                    throw nne;
788                                }
789            }
790            bean = nestedBean;
791            name = getResolver().remove(name);
792        }
793
794        // Remove any subscript from the final name value
795        name = getResolver().getProperty(name);
796
797        // Special handling for DynaBeans
798        if (bean instanceof DynaBean) {
799            DynaProperty descriptor =
800                    ((DynaBean) bean).getDynaClass().getDynaProperty(name);
801            if (descriptor == null) {
802                return (null);
803            }
804            Class type = descriptor.getType();
805            if (type == null) {
806                return (null);
807            } else if (type.isArray()) {
808                return (type.getComponentType());
809            } else {
810                return (type);
811            }
812        }
813
814        PropertyDescriptor descriptor =
815                getPropertyDescriptor(bean, name);
816        if (descriptor == null) {
817            return (null);
818        } else if (descriptor instanceof IndexedPropertyDescriptor) {
819            return (((IndexedPropertyDescriptor) descriptor).
820                    getIndexedPropertyType());
821        } else if (descriptor instanceof MappedPropertyDescriptor) {
822            return (((MappedPropertyDescriptor) descriptor).
823                    getMappedPropertyType());
824        } else {
825            return (descriptor.getPropertyType());
826        }
827
828    }
829}