View Javadoc

1   /**
2    * Copyright 2005-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.datadictionary.uif;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.commons.logging.Log;
20  import org.apache.commons.logging.LogFactory;
21  import org.kuali.rice.krad.datadictionary.DataDictionaryException;
22  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
23  import org.kuali.rice.krad.uif.UifConstants;
24  import org.kuali.rice.krad.uif.util.ViewModelUtils;
25  import org.kuali.rice.krad.uif.view.View;
26  import org.kuali.rice.krad.uif.service.ViewTypeService;
27  import org.springframework.beans.PropertyValues;
28  import org.springframework.beans.factory.config.BeanDefinition;
29  import org.springframework.beans.factory.support.KualiDefaultListableBeanFactory;
30  import org.kuali.rice.krad.uif.UifConstants.ViewType;
31  
32  import java.util.ArrayList;
33  import java.util.HashMap;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.Map.Entry;
37  
38  /**
39   * Indexes <code>View</code> bean entries for retrieval
40   *
41   * <p>
42   * This is used to retrieve a <code>View</code> instance by its unique id.
43   * Furthermore, view of certain types (that have a <code>ViewTypeService</code>
44   * are indexed by their type to support retrieval of views based on parameters.
45   * </p>
46   *
47   * @author Kuali Rice Team (rice.collab@kuali.org)
48   */
49  public class UifDictionaryIndex implements Runnable {
50      private static final Log LOG = LogFactory.getLog(UifDictionaryIndex.class);
51  
52      private KualiDefaultListableBeanFactory ddBeans;
53  
54      // view entries keyed by view id with value the spring bean name
55      private Map<String, String> viewBeanEntriesById;
56  
57      // view entries indexed by type
58      private Map<String, ViewTypeDictionaryIndex> viewEntriesByType;
59  
60      // views that are loaded eagerly
61      private Map<String, UifViewPool> viewPools;
62  
63      public UifDictionaryIndex(KualiDefaultListableBeanFactory ddBeans) {
64          this.ddBeans = ddBeans;
65      }
66  
67      public void run() {
68          LOG.info("Starting View Index Building");
69          buildViewIndicies();
70          LOG.info("Completed View Index Building");
71      }
72  
73      /**
74       * Retrieves the View instance with the given id
75       * 
76       * <p>
77       * First an attempt is made to get a preloaded view (if one exists). If found it is pulled from
78       * the pool and a replacement is built on another thread. If a preloaded view does not exist, one is built
79       * by Spring from the bean factory
80       * </p>
81       *
82       * @param viewId - the unique id for the view
83       * @return View instance with the given id
84       * @throws org.kuali.rice.krad.datadictionary.DataDictionaryException if view doesn't exist for id
85       */
86      public View getViewById(final String viewId) {
87          // check for preloaded view
88          if (viewPools.containsKey(viewId)) {
89              View view = null;
90  
91              final UifViewPool viewPool = viewPools.get(viewId);
92              synchronized (viewPool) {
93                  if (!viewPool.isEmpty()) {
94                      view = viewPool.getViewInstance();
95  
96                      // replace view in the pool
97                      Runnable createView = new Runnable() {
98                          public void run() {
99                              View newViewInstance = getViewInstanceFromFactory(viewId);
100                             viewPool.addViewInstance(newViewInstance);
101                         }
102                     };
103 
104                     Thread t = new Thread(createView);
105                     t.start();
106 
107                     return view;
108                 } else {
109                     LOG.info("Pool size for view with id: "
110                             + viewId
111                             + " is empty. Considering increasing max pool size.");
112                 }
113             }
114         }
115 
116         // no pooling, get new instance from factory
117         return getViewInstanceFromFactory(viewId);
118     }
119 
120     /**
121      * Retrieves a view object from the bean factory based on view id
122      *
123      * @param viewId - id of the view to retrieve
124      * @return View instance for view with specified id
125      * @throws org.kuali.rice.krad.datadictionary.DataDictionaryException if view doesn't exist for id
126      */
127     protected View getViewInstanceFromFactory(String viewId) {
128         String beanName = viewBeanEntriesById.get(viewId);
129         if (StringUtils.isBlank(beanName)) {
130             throw new DataDictionaryException("Unable to find View with id: " + viewId);
131         }
132 
133         return ddBeans.getBean(beanName, View.class);  
134     }
135 
136     /**
137      * Retrieves a <code>View</code> instance that is of the given type based on
138      * the index key
139      *
140      * @param viewTypeName - type name for the view
141      * @param indexKey - Map of index key parameters, these are the parameters the
142      * indexer used to index the view initially and needs to identify
143      * an unique view instance
144      * @return View instance that matches the given index or Null if one is not
145      *         found
146      */
147     public View getViewByTypeIndex(ViewType viewTypeName, Map<String, String> indexKey) {
148         String index = buildTypeIndex(indexKey);
149 
150         ViewTypeDictionaryIndex typeIndex = getTypeIndex(viewTypeName);
151 
152         String viewId = typeIndex.get(index);
153         if (StringUtils.isNotBlank(viewId)) {
154             return getViewById(viewId);
155         }
156 
157         return null;
158     }
159 
160     /**
161      * Indicates whether a <code>View</code> exists for the given view type and index information
162      *
163      * @param viewTypeName - type name for the view
164      * @param indexKey - Map of index key parameters, these are the parameters the indexer used to index
165      * the view initially and needs to identify an unique view instance
166      * @return boolean true if view exists, false if not
167      */
168     public boolean viewByTypeExist(ViewType viewTypeName, Map<String, String> indexKey) {
169         boolean viewExist = false;
170 
171         String index = buildTypeIndex(indexKey);
172         ViewTypeDictionaryIndex typeIndex = getTypeIndex(viewTypeName);
173 
174         String viewId = typeIndex.get(index);
175         if (StringUtils.isNotBlank(viewId)) {
176             viewExist = true;
177         }
178 
179         return viewExist;
180     }
181 
182     /**
183      * Retrieves the configured property values for the view bean definition associated with the given id
184      *
185      * <p>
186      * Since constructing the View object can be expensive, when metadata only is needed this method can be used
187      * to retrieve the configured property values. Note this looks at the merged bean definition
188      * </p>
189      *
190      * @param viewId - id for the view to retrieve
191      * @return PropertyValues configured on the view bean definition, or null if view is not found
192      */
193     public PropertyValues getViewPropertiesById(String viewId) {
194         String beanName = viewBeanEntriesById.get(viewId);
195         if (StringUtils.isBlank(beanName)) {
196             BeanDefinition beanDefinition = ddBeans.getMergedBeanDefinition(beanName);
197 
198             return beanDefinition.getPropertyValues();
199         }
200 
201         return null;
202     }
203 
204     /**
205      * Retrieves the configured property values for the view bean definition associated with the given type and
206      * index
207      *
208      * <p>
209      * Since constructing the View object can be expensive, when metadata only is needed this method can be used
210      * to retrieve the configured property values. Note this looks at the merged bean definition
211      * </p>
212      *
213      * @param viewTypeName - type name for the view
214      * @param indexKey - Map of index key parameters, these are the parameters the indexer used to index
215      * the view initially and needs to identify an unique view instance
216      * @return PropertyValues configured on the view bean definition, or null if view is not found
217      */
218     public PropertyValues getViewPropertiesByType(ViewType viewTypeName, Map<String, String> indexKey) {
219         String index = buildTypeIndex(indexKey);
220 
221         ViewTypeDictionaryIndex typeIndex = getTypeIndex(viewTypeName);
222 
223         String beanName = typeIndex.get(index);
224         if (StringUtils.isNotBlank(beanName)) {
225             BeanDefinition beanDefinition = ddBeans.getMergedBeanDefinition(beanName);
226 
227             return beanDefinition.getPropertyValues();
228         }
229 
230         return null;
231     }
232 
233     /**
234      * Gets all <code>View</code> prototypes configured for the given view type
235      * name
236      *
237      * @param viewTypeName - view type name to retrieve
238      * @return List<View> view prototypes with the given type name, or empty
239      *         list
240      */
241     public List<View> getViewsForType(ViewType viewTypeName) {
242         List<View> typeViews = new ArrayList<View>();
243 
244         // get view ids for the type
245         if (viewEntriesByType.containsKey(viewTypeName.name())) {
246             ViewTypeDictionaryIndex typeIndex = viewEntriesByType.get(viewTypeName.name());
247             for (Entry<String, String> typeEntry : typeIndex.getViewIndex().entrySet()) {
248                 View typeView = ddBeans.getBean(typeEntry.getValue(), View.class);
249                 typeViews.add(typeView);
250             }
251         } else {
252             throw new DataDictionaryException("Unable to find view index for type: " + viewTypeName);
253         }
254 
255         return typeViews;
256     }
257 
258     /**
259      * Initializes the view index <code>Map</code> then iterates through all the
260      * beans in the factory that implement <code>View</code>, adding them to the
261      * index
262      */
263     protected void buildViewIndicies() {
264         viewBeanEntriesById = new HashMap<String, String>();
265         viewEntriesByType = new HashMap<String, ViewTypeDictionaryIndex>();
266         viewPools = new HashMap<String, UifViewPool>();
267 
268         String[] beanNames = ddBeans.getBeanNamesForType(View.class);
269         for (int i = 0; i < beanNames.length; i++) {
270             final String beanName = beanNames[i];
271             BeanDefinition beanDefinition = ddBeans.getMergedBeanDefinition(beanName);
272             PropertyValues propertyValues = beanDefinition.getPropertyValues();
273 
274             String id = ViewModelUtils.getStringValFromPVs(propertyValues, "id");
275             if (StringUtils.isBlank(id)) {
276                 id = beanName;
277             }
278 
279             if (viewBeanEntriesById.containsKey(id)) {
280                 throw new DataDictionaryException("Two views must not share the same id. Found duplicate id: " + id);
281             }
282             viewBeanEntriesById.put(id, beanName);
283 
284             indexViewForType(propertyValues, id);
285 
286             // pre-load views if necessary
287             String poolSizeStr = ViewModelUtils.getStringValFromPVs(propertyValues, "preloadPoolSize");
288             if (StringUtils.isNotBlank(poolSizeStr)) {
289                 int poolSize = Integer.parseInt(poolSizeStr);
290                 if (poolSize < 1) {
291                     continue;
292                 }
293 
294                 final UifViewPool viewPool = new UifViewPool();
295                 viewPool.setMaxSize(poolSize);
296                 for (int j = 0; j < poolSize; j++) {
297                     Runnable createView = new Runnable() {
298                         public void run() {
299                             View view = (View) ddBeans.getBean(beanName);
300                             viewPool.addViewInstance(view);
301                         }
302                     };
303 
304                     Thread t = new Thread(createView);
305                     t.start();
306                 }
307 
308                 viewPools.put(id, viewPool);
309             }
310         }
311     }
312 
313     /**
314      * Performs additional indexing based on the view type associated with the view instance. The
315      * <code>ViewTypeService</code> associated with the view type name on the instance is invoked to retrieve
316      * the parameter key/value pairs from the configured property values, which are then used to build up an index
317      * used to key the entry
318      *
319      * @param propertyValues - property values configured on the view bean definition
320      * @param beanName - id (or bean name if id was not set) for the view
321      */
322     protected void indexViewForType(PropertyValues propertyValues, String id) {
323         String viewTypeName = ViewModelUtils.getStringValFromPVs(propertyValues, "viewTypeName");
324         if (StringUtils.isBlank(viewTypeName)) {
325             return;
326         }
327 
328         UifConstants.ViewType viewType = ViewType.valueOf(viewTypeName);
329 
330         ViewTypeService typeService = KRADServiceLocatorWeb.getViewService().getViewTypeService(viewType);
331         if (typeService == null) {
332             // don't do any further indexing
333             return;
334         }
335 
336         // invoke type service to retrieve it parameter name/value pairs
337         Map<String, String> typeParameters = typeService.getParametersFromViewConfiguration(propertyValues);
338 
339         // build the index string from the parameters
340         String index = buildTypeIndex(typeParameters);
341 
342         // get the index for the type and add the view entry
343         ViewTypeDictionaryIndex typeIndex = getTypeIndex(viewType);
344 
345         typeIndex.put(index, id);
346     }
347 
348     /**
349      * Retrieves the <code>ViewTypeDictionaryIndex</code> instance for the given
350      * view type name. If one does not exist yet for the given name, a new
351      * instance is created
352      *
353      * @param viewType - name of the view type to retrieve index for
354      * @return ViewTypeDictionaryIndex instance
355      */
356     protected ViewTypeDictionaryIndex getTypeIndex(UifConstants.ViewType viewType) {
357         ViewTypeDictionaryIndex typeIndex = null;
358 
359         if (viewEntriesByType.containsKey(viewType.name())) {
360             typeIndex = viewEntriesByType.get(viewType.name());
361         } else {
362             typeIndex = new ViewTypeDictionaryIndex();
363             viewEntriesByType.put(viewType.name(), typeIndex);
364         }
365 
366         return typeIndex;
367     }
368 
369     /**
370      * Builds up an index string from the given Map of parameters
371      *
372      * @param typeParameters - Map of parameters to use for index
373      * @return String index
374      */
375     protected String buildTypeIndex(Map<String, String> typeParameters) {
376         String index = "";
377 
378         for (String parameterName : typeParameters.keySet()) {
379             if (StringUtils.isNotBlank(index)) {
380                 index += "|||";
381             }
382             index += parameterName + "^^" + typeParameters.get(parameterName);
383         }
384 
385         return index;
386     }
387 
388 }