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 }