View Javadoc

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