001    /*
002     * Copyright (c) 2008, Your Corporation. All Rights Reserved.
003     */
004    
005    package org.chenillekit.tapestry.core.models;
006    
007    import java.util.List;
008    import java.util.Map;
009    
010    import org.apache.tapestry5.OptionGroupModel;
011    import org.apache.tapestry5.OptionModel;
012    import org.apache.tapestry5.internal.OptionModelImpl;
013    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
014    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
015    import org.apache.tapestry5.util.AbstractSelectModel;
016    
017    import ognl.Ognl;
018    
019    /**
020     * Defines the possible options for a <select> [X]HTML element.
021     * This select model implementation based on <a href="http://www.ognl.org/">OGNL</a>.
022     * <p/>
023     * <h4>Use Case</h4>
024     * your object list contains entities of type <code>User</code>. Each <code>User</code> object contains
025     * an object typed <code>Address</code>, and every <code>Address</code> object contains
026     * a property <code>name</code>.
027     * <p/>
028     * If you want to display in the list the value of the name property you should define a
029     * <code>labelExpression</code> like <code>address.name</code>
030     *
031     * @version $Id: OgnlSelectModel.java 670 2010-07-19 09:22:02Z mlusetti $
032     */
033    public class OgnlSelectModel<T> extends AbstractSelectModel
034    {
035        private static final Map<String, Object> _cache = CollectionFactory.newMap();
036    
037        private List<T> _objectList;
038        private String _labelExpression;
039    
040        /**
041         * constructs an ognl select model.
042         *
043         * @param labelExpression the ognl expression converted to label.
044         */
045        public OgnlSelectModel(String labelExpression)
046        {
047            this(null, labelExpression);
048        }
049    
050        /**
051         * constructs an ognl select model.
052         *
053         * @param objectList      list of objects that should listed by select tag
054         * @param labelExpression the ognl expression converted to label.
055         */
056        public OgnlSelectModel(List<T> objectList, String labelExpression)
057        {
058            assert InternalUtils.isNonBlank(labelExpression);
059    
060            _labelExpression = labelExpression;
061    
062            // more carfully i think, so we copy the object list
063            if (objectList != null)
064                setObjectList(objectList);
065        }
066    
067        /**
068         * set the object list.
069         *
070         * @param objectList the object list
071         */
072        public void setObjectList(List<T> objectList)
073        {
074            _objectList = CollectionFactory.newList(objectList);
075        }
076    
077        /**
078         * The list of groups, returns always null
079         *
080         * @return always null
081         */
082        public List<OptionGroupModel> getOptionGroups()
083        {
084            return null;
085        }
086    
087        /**
088         * The list of ungrouped options, which appear after any grouped options.
089         *
090         * @return the ungrouped options
091         */
092        public List<OptionModel> getOptions()
093        {
094            List<OptionModel> optionModelList = CollectionFactory.newList();
095    
096            if (_objectList != null)
097            {
098                for (int i = 1; i <= _objectList.size(); i++)
099                {
100                    String label = String.valueOf(getValue(_objectList.get(i - 1), _labelExpression));
101                    optionModelList.add(new OptionModelImpl(label, _objectList.get(i - 1)));
102                }
103            }
104    
105            return optionModelList;
106        }
107    
108        /**
109         * Gets a parsed OGNL expression from the input string.
110         *
111         * @param expression the ognl expression.
112         *
113         * @return value of evaluated expression
114         */
115        private synchronized Object getParsedExpression(String expression)
116        {
117            Object result = _cache.get(expression);
118    
119            if (result == null)
120            {
121                try
122                {
123                    result = Ognl.parseExpression(expression);
124                }
125                catch (Exception ex)
126                {
127                    throw new RuntimeException(ex);
128                }
129    
130                _cache.put(expression, result);
131            }
132    
133            return result;
134        }
135    
136        /**
137         * Reads the current value of the property (or other resource). When reading properties of
138         * objects that are primitive types, this will return an instance of the wrapper type. In some
139         * cases, a binding is read only and this method will throw a runtime exception.
140         *
141         * @param rootObject
142         * @param expression the ognl expression.
143         *
144         * @return value of evaluated expression
145         */
146        private Object getValue(Object rootObject, String expression)
147        {
148            Object ognlExpression = getParsedExpression(expression);
149    
150            try
151            {
152                Map context = Ognl.createDefaultContext(rootObject);
153                return Ognl.getValue(ognlExpression, context, rootObject);
154            }
155            catch (Exception ex)
156            {
157                throw new RuntimeException(ex);
158            }
159        }
160    }