001    /**
002     * Copyright 2005-2013 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    package org.kuali.rice.krad.datadictionary;
017    
018    import org.apache.commons.lang.StringUtils;
019    import org.apache.commons.logging.Log;
020    import org.apache.commons.logging.LogFactory;
021    import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
022    import org.kuali.rice.krad.uif.UifConstants;
023    import org.kuali.rice.krad.uif.component.Component;
024    import org.kuali.rice.krad.uif.util.ComponentUtils;
025    import org.kuali.rice.krad.uif.util.ViewModelUtils;
026    import org.kuali.rice.krad.uif.view.View;
027    import org.kuali.rice.krad.uif.service.ViewTypeService;
028    import org.springframework.beans.PropertyValues;
029    import org.springframework.beans.factory.config.BeanDefinition;
030    import org.springframework.beans.factory.support.KualiDefaultListableBeanFactory;
031    import org.kuali.rice.krad.uif.UifConstants.ViewType;
032    
033    import java.util.ArrayList;
034    import java.util.HashMap;
035    import java.util.List;
036    import java.util.Map;
037    import java.util.Map.Entry;
038    
039    /**
040     * Indexes <code>View</code> bean entries for retrieval
041     *
042     * <p>
043     * This is used to retrieve a <code>View</code> instance by its unique id.
044     * Furthermore, view of certain types (that have a <code>ViewTypeService</code>
045     * are indexed by their type to support retrieval of views based on parameters.
046     * </p>
047     *
048     * @author Kuali Rice Team (rice.collab@kuali.org)
049     */
050    public class UifDictionaryIndex implements Runnable {
051        private static final Log LOG = LogFactory.getLog(UifDictionaryIndex.class);
052    
053        private KualiDefaultListableBeanFactory ddBeans;
054    
055        // view entries keyed by view id with value the spring bean name
056        private Map<String, String> viewBeanEntriesById;
057    
058        // view entries indexed by type
059        private Map<String, ViewTypeDictionaryIndex> viewEntriesByType;
060    
061        public UifDictionaryIndex(KualiDefaultListableBeanFactory ddBeans) {
062            this.ddBeans = ddBeans;
063        }
064    
065        public void run() {
066            LOG.info("Starting View Index Building");
067            buildViewIndicies();
068            LOG.info("Completed View Index Building");
069        }
070    
071        /**
072         * Retrieves the View instance with the given id from the bean factory.
073         * Since View instances are stateful, we need to get a new instance from
074         * Spring each time.
075         *
076         * @param viewId - the unique id for the view
077         * @return <code>View</code> instance
078         */
079        public View getViewById(String viewId) {
080            String beanName = viewBeanEntriesById.get(viewId);
081            if (StringUtils.isBlank(beanName)) {
082                throw new DataDictionaryException("Unable to find View with id: " + viewId);
083            }
084    
085            return ddBeans.getBean(beanName, View.class);
086        }
087    
088        /**
089         * Retrieves a <code>View</code> instance that is of the given type based on
090         * the index key
091         *
092         * @param viewTypeName - type name for the view
093         * @param indexKey - Map of index key parameters, these are the parameters the
094         * indexer used to index the view initially and needs to identify
095         * an unique view instance
096         * @return View instance that matches the given index or Null if one is not
097         *         found
098         */
099        public View getViewByTypeIndex(ViewType viewTypeName, Map<String, String> indexKey) {
100            String index = buildTypeIndex(indexKey);
101    
102            ViewTypeDictionaryIndex typeIndex = getTypeIndex(viewTypeName);
103    
104            String beanName = typeIndex.get(index);
105            if (StringUtils.isNotBlank(beanName)) {
106                return ddBeans.getBean(beanName, View.class);
107            }
108    
109            return null;
110        }
111    
112        /**
113         * Indicates whether a <code>View</code> exists for the given view type and index information
114         *
115         * @param viewTypeName - type name for the view
116         * @param indexKey - Map of index key parameters, these are the parameters the indexer used to index
117         * the view initially and needs to identify an unique view instance
118         * @return boolean true if view exists, false if not
119         */
120        public boolean viewByTypeExist(ViewType viewTypeName, Map<String, String> indexKey) {
121            boolean viewExist = false;
122    
123            String index = buildTypeIndex(indexKey);
124            ViewTypeDictionaryIndex typeIndex = getTypeIndex(viewTypeName);
125    
126            String beanName = typeIndex.get(index);
127            if (StringUtils.isNotBlank(beanName)) {
128                viewExist = true;
129            }
130    
131            return viewExist;
132        }
133    
134        /**
135         * Retrieves the configured property values for the view bean definition associated with the given id
136         *
137         * <p>
138         * Since constructing the View object can be expensive, when metadata only is needed this method can be used
139         * to retrieve the configured property values. Note this looks at the merged bean definition
140         * </p>
141         *
142         * @param viewId - id for the view to retrieve
143         * @return PropertyValues configured on the view bean definition, or null if view is not found
144         */
145        public PropertyValues getViewPropertiesById(String viewId) {
146            String beanName = viewBeanEntriesById.get(viewId);
147            if (StringUtils.isBlank(beanName)) {
148                BeanDefinition beanDefinition = ddBeans.getMergedBeanDefinition(beanName);
149    
150                return beanDefinition.getPropertyValues();
151            }
152    
153            return null;
154        }
155    
156        /**
157         * Retrieves the configured property values for the view bean definition associated with the given type and
158         * index
159         *
160         * <p>
161         * Since constructing the View object can be expensive, when metadata only is needed this method can be used
162         * to retrieve the configured property values. Note this looks at the merged bean definition
163         * </p>
164         *
165         * @param viewTypeName - type name for the view
166         * @param indexKey - Map of index key parameters, these are the parameters the indexer used to index
167         * the view initially and needs to identify an unique view instance
168         * @return PropertyValues configured on the view bean definition, or null if view is not found
169         */
170        public PropertyValues getViewPropertiesByType(ViewType viewTypeName, Map<String, String> indexKey) {
171            String index = buildTypeIndex(indexKey);
172    
173            ViewTypeDictionaryIndex typeIndex = getTypeIndex(viewTypeName);
174    
175            String beanName = typeIndex.get(index);
176            if (StringUtils.isNotBlank(beanName)) {
177                BeanDefinition beanDefinition = ddBeans.getMergedBeanDefinition(beanName);
178    
179                return beanDefinition.getPropertyValues();
180            }
181    
182            return null;
183        }
184    
185        /**
186         * Gets all <code>View</code> prototypes configured for the given view type
187         * name
188         *
189         * @param viewTypeName - view type name to retrieve
190         * @return List<View> view prototypes with the given type name, or empty
191         *         list
192         */
193        public List<View> getViewsForType(ViewType viewTypeName) {
194            List<View> typeViews = new ArrayList<View>();
195    
196            // get view ids for the type
197            if (viewEntriesByType.containsKey(viewTypeName.name())) {
198                ViewTypeDictionaryIndex typeIndex = viewEntriesByType.get(viewTypeName.name());
199                for (Entry<String, String> typeEntry : typeIndex.getViewIndex().entrySet()) {
200                    View typeView = ddBeans.getBean(typeEntry.getValue(), View.class);
201                    typeViews.add(typeView);
202                }
203            } else {
204                throw new DataDictionaryException("Unable to find view index for type: " + viewTypeName);
205            }
206    
207            return typeViews;
208        }
209    
210        /**
211         * Initializes the view index <code>Map</code> then iterates through all the
212         * beans in the factory that implement <code>View</code>, adding them to the
213         * index
214         */
215        protected void buildViewIndicies() {
216            viewBeanEntriesById = new HashMap<String, String>();
217            viewEntriesByType = new HashMap<String, ViewTypeDictionaryIndex>();
218    
219            String[] beanNames = ddBeans.getBeanNamesForType(View.class);
220            for (int i = 0; i < beanNames.length; i++) {
221                String beanName = beanNames[i];
222                BeanDefinition beanDefinition = ddBeans.getMergedBeanDefinition(beanName);
223                PropertyValues propertyValues = beanDefinition.getPropertyValues();
224    
225                String id = ViewModelUtils.getStringValFromPVs(propertyValues, "id");
226                if (StringUtils.isBlank(id)) {
227                    id = beanName;
228                }
229    
230                if (viewBeanEntriesById.containsKey(id)) {
231                    throw new DataDictionaryException("Two views must not share the same id. Found duplicate id: " + id);
232                }
233                viewBeanEntriesById.put(id, beanName);
234    
235                indexViewForType(propertyValues, beanName);
236            }
237        }
238    
239        /**
240         * Performs additional indexing based on the view type associated with the view instance. The
241         * <code>ViewTypeService</code> associated with the view type name on the instance is invoked to retrieve
242         * the parameter key/value pairs from the configured property values, which are then used to build up an index
243         * used to key the entry
244         *
245         * @param propertyValues - property values configured on the view bean definition
246         * @param beanName - name of the view's bean in Spring
247         */
248        protected void indexViewForType(PropertyValues propertyValues, String beanName) {
249            String viewTypeName = ViewModelUtils.getStringValFromPVs(propertyValues,  "viewTypeName");
250            if (StringUtils.isBlank(viewTypeName)) {
251                return;
252            }
253    
254            UifConstants.ViewType viewType = ViewType.valueOf(viewTypeName);
255    
256            ViewTypeService typeService = KRADServiceLocatorWeb.getViewService().getViewTypeService(viewType);
257            if (typeService == null) {
258                // don't do any further indexing
259                return;
260            }
261    
262            // invoke type service to retrieve it parameter name/value pairs
263            Map<String, String> typeParameters = typeService.getParametersFromViewConfiguration(propertyValues);
264    
265            // build the index string from the parameters
266            String index = buildTypeIndex(typeParameters);
267    
268            // get the index for the type and add the view entry
269            ViewTypeDictionaryIndex typeIndex = getTypeIndex(viewType);
270    
271            typeIndex.put(index, beanName);
272        }
273    
274        /**
275         * Retrieves the <code>ViewTypeDictionaryIndex</code> instance for the given
276         * view type name. If one does not exist yet for the given name, a new
277         * instance is created
278         *
279         * @param viewType - name of the view type to retrieve index for
280         * @return ViewTypeDictionaryIndex instance
281         */
282        protected ViewTypeDictionaryIndex getTypeIndex(UifConstants.ViewType viewType) {
283            ViewTypeDictionaryIndex typeIndex = null;
284    
285            if (viewEntriesByType.containsKey(viewType.name())) {
286                typeIndex = viewEntriesByType.get(viewType.name());
287            } else {
288                typeIndex = new ViewTypeDictionaryIndex();
289                viewEntriesByType.put(viewType.name(), typeIndex);
290            }
291    
292            return typeIndex;
293        }
294    
295        /**
296         * Builds up an index string from the given Map of parameters
297         *
298         * @param typeParameters - Map of parameters to use for index
299         * @return String index
300         */
301        protected String buildTypeIndex(Map<String, String> typeParameters) {
302            String index = "";
303    
304            for (String parameterName : typeParameters.keySet()) {
305                if (StringUtils.isNotBlank(index)) {
306                    index += "|||";
307                }
308                index += parameterName + "^^" + typeParameters.get(parameterName);
309            }
310    
311            return index;
312        }
313    
314    }