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 */
016package org.kuali.rice.krad.datadictionary;
017
018import org.apache.commons.lang.StringUtils;
019import org.apache.commons.logging.Log;
020import org.apache.commons.logging.LogFactory;
021import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
022import org.kuali.rice.krad.uif.UifConstants;
023import org.kuali.rice.krad.uif.component.Component;
024import org.kuali.rice.krad.uif.util.ComponentUtils;
025import org.kuali.rice.krad.uif.util.ViewModelUtils;
026import org.kuali.rice.krad.uif.view.View;
027import org.kuali.rice.krad.uif.service.ViewTypeService;
028import org.springframework.beans.PropertyValues;
029import org.springframework.beans.factory.config.BeanDefinition;
030import org.springframework.beans.factory.support.KualiDefaultListableBeanFactory;
031import org.kuali.rice.krad.uif.UifConstants.ViewType;
032
033import java.util.ArrayList;
034import java.util.HashMap;
035import java.util.List;
036import java.util.Map;
037import 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 */
050public 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}