View Javadoc

1   /**
2    * Copyright 2005-2014 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 java.util.ArrayList;
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.Map.Entry;
23  
24  import org.apache.commons.lang.StringUtils;
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.kuali.rice.core.api.config.property.ConfigContext;
28  import org.kuali.rice.krad.datadictionary.DataDictionaryException;
29  import org.kuali.rice.krad.datadictionary.DefaultListableBeanFactory;
30  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
31  import org.kuali.rice.krad.uif.UifConstants;
32  import org.kuali.rice.krad.uif.UifConstants.ViewType;
33  import org.kuali.rice.krad.uif.service.ViewTypeService;
34  import org.kuali.rice.krad.uif.util.ComponentUtils;
35  import org.kuali.rice.krad.uif.util.ViewModelUtils;
36  import org.kuali.rice.krad.uif.view.View;
37  import org.kuali.rice.krad.util.KRADConstants;
38  import org.springframework.beans.PropertyValues;
39  import org.springframework.beans.factory.config.BeanDefinition;
40  
41  /**
42   * Indexes {@code View} bean entries for retrieval
43   *
44   * <p>
45   * This is used to retrieve a {@code View} instance by its unique id.
46   * Furthermore, view of certain types (that have a {@code ViewTypeService}
47   * are indexed by their type to support retrieval of views based on parameters.
48   * </p>
49   *
50   * @author Kuali Rice Team (rice.collab@kuali.org)
51   */
52  public class UifDictionaryIndex implements Runnable {
53      private static final Log LOG = LogFactory.getLog(UifDictionaryIndex.class);
54  
55      private static final int VIEW_CACHE_SIZE = 1000;
56  
57      private DefaultListableBeanFactory ddBeans;
58  
59      // view entries keyed by view id with value the spring bean name
60      private Map<String, String> viewBeanEntriesById;
61  
62      // view entries indexed by type
63      private Map<String, ViewTypeDictionaryIndex> viewEntriesByType;
64  
65      // Cache to hold previously-built view definitions
66      protected Map<String,View> viewCache = new HashMap<String, View>(VIEW_CACHE_SIZE);
67  
68      public UifDictionaryIndex(DefaultListableBeanFactory ddBeans) {
69          this.ddBeans = ddBeans;
70      }
71  
72      @Override
73      public void run() {
74          buildViewIndicies();
75      }
76  
77      /**
78       * Retrieves the View instance with the given id
79       *
80       * <p>
81       * First an attempt is made to get a cached view (if one exists). If found it is pulled from
82       * the cache and cloned to preserve the integrity of the cache.  If not already cached, one is built
83       * by Spring from the bean factory and then cloned.
84       * </p>
85       *
86       * @param viewId the unique id for the view
87       * @return View instance with the given id
88       * @throws org.kuali.rice.krad.datadictionary.DataDictionaryException if view doesn't exist for id
89       */
90      public View getViewById(String viewId) {
91          View cachedView = viewCache.get(viewId);
92          if ( cachedView == null ) {
93              if ( LOG.isDebugEnabled() ) {
94                  LOG.debug( "View " + viewId + " not in cache - creating and storing to cache");
95              }
96  
97              String beanName = viewBeanEntriesById.get(viewId);
98              if (StringUtils.isBlank(beanName)) {
99                  throw new DataDictionaryException("Unable to find View with id: " + viewId);
100             }
101 
102             View newView = ddBeans.getBean(beanName, View.class);
103 
104             boolean inDevMode = ConfigContext.getCurrentContextConfig().getBooleanProperty(
105                     KRADConstants.ConfigParameters.KRAD_DEV_MODE);
106 
107             if (!inDevMode) {
108                 synchronized (viewCache) {
109                     viewCache.put(viewId, newView);
110                 }
111             }
112 
113             cachedView = newView;
114         } else {
115             if ( LOG.isDebugEnabled() ) {
116                 LOG.debug("Pulled view " + viewId + " from Cache.  Cloning..." );
117             }
118         }
119 
120         View clonedView = ComponentUtils.copy(cachedView);
121 
122         return clonedView;
123     }
124 
125     /**
126      * Gets a view instance from the pool or factory but does not replace the view, meant for view readonly
127      * access (not running the lifecycle but just checking configuration)
128      *
129      * @param viewId the unique id for the view
130      * @return View instance with the given id
131      */
132     public View getImmutableViewById(String viewId) {
133         // check for preloaded view
134         View cachedView = viewCache.get(viewId);
135 
136         // If not already cached, pull and cache as normal
137         // This makes the first call to this method more expensive, as it
138         // will get a cloned copy instead of the original from the cache
139         if ( cachedView == null ) {
140             return getViewById(viewId);
141         }
142 
143         return cachedView;
144     }
145 
146     /**
147      * Retrieves a {@code View} instance that is of the given type based on
148      * the index key
149      *
150      * @param viewTypeName - type name for the view
151      * @param indexKey - Map of index key parameters, these are the parameters the
152      * indexer used to index the view initially and needs to identify
153      * an unique view instance
154      * @return View instance that matches the given index or Null if one is not
155      *         found
156      */
157     public View getViewByTypeIndex(ViewType viewTypeName, Map<String, String> indexKey) {
158         String viewId = getViewIdByTypeIndex(viewTypeName, indexKey);
159         if (StringUtils.isNotBlank(viewId)) {
160             return getViewById(viewId);
161         }
162 
163         return null;
164     }
165 
166     /**
167      * Retrieves the id for the view that is associated with the given view type and index key
168      *
169      * @param viewTypeName type name for the view
170      * @param indexKey Map of index key parameters, these are the parameters the
171      * indexer used to index the view initially and needs to identify an unique view instance
172      * @return id for the view that matches the view type and index or null if a match is not found
173      */
174     public String getViewIdByTypeIndex(ViewType viewTypeName, Map<String, String> indexKey) {
175         String index = buildTypeIndex(indexKey);
176 
177         ViewTypeDictionaryIndex typeIndex = getTypeIndex(viewTypeName);
178 
179         return typeIndex.get(index);
180     }
181 
182     /**
183      * Indicates whether a {@code View} exists for the given view type and index information
184      *
185      * @param viewTypeName - type name for the view
186      * @param indexKey - Map of index key parameters, these are the parameters the indexer used to index
187      * the view initially and needs to identify an unique view instance
188      * @return boolean true if view exists, false if not
189      */
190     public boolean viewByTypeExist(ViewType viewTypeName, Map<String, String> indexKey) {
191         boolean viewExist = false;
192 
193         String index = buildTypeIndex(indexKey);
194         ViewTypeDictionaryIndex typeIndex = getTypeIndex(viewTypeName);
195 
196         String viewId = typeIndex.get(index);
197         if (StringUtils.isNotBlank(viewId)) {
198             viewExist = true;
199         }
200 
201         return viewExist;
202     }
203 
204     /**
205      * Retrieves the configured property values for the view bean definition associated with the given id
206      *
207      * <p>
208      * Since constructing the View object can be expensive, when metadata only is needed this method can be used
209      * to retrieve the configured property values. Note this looks at the merged bean definition
210      * </p>
211      *
212      * @param viewId - id for the view to retrieve
213      * @return PropertyValues configured on the view bean definition, or null if view is not found
214      */
215     public PropertyValues getViewPropertiesById(String viewId) {
216         String beanName = viewBeanEntriesById.get(viewId);
217         if (StringUtils.isNotBlank(beanName)) {
218             BeanDefinition beanDefinition = ddBeans.getMergedBeanDefinition(beanName);
219 
220             return beanDefinition.getPropertyValues();
221         }
222 
223         return null;
224     }
225 
226     /**
227      * Retrieves the configured property values for the view bean definition associated with the given type and
228      * index
229      *
230      * <p>
231      * Since constructing the View object can be expensive, when metadata only is needed this method can be used
232      * to retrieve the configured property values. Note this looks at the merged bean definition
233      * </p>
234      *
235      * @param viewTypeName - type name for the view
236      * @param indexKey - Map of index key parameters, these are the parameters the indexer used to index
237      * the view initially and needs to identify an unique view instance
238      * @return PropertyValues configured on the view bean definition, or null if view is not found
239      */
240     public PropertyValues getViewPropertiesByType(ViewType viewTypeName, Map<String, String> indexKey) {
241         String index = buildTypeIndex(indexKey);
242 
243         ViewTypeDictionaryIndex typeIndex = getTypeIndex(viewTypeName);
244 
245         String beanName = typeIndex.get(index);
246         if (StringUtils.isNotBlank(beanName)) {
247             BeanDefinition beanDefinition = ddBeans.getMergedBeanDefinition(beanName);
248 
249             return beanDefinition.getPropertyValues();
250         }
251 
252         return null;
253     }
254 
255     /**
256      * Gets all {@code View} prototypes configured for the given view type
257      * name
258      *
259      * @param viewTypeName - view type name to retrieve
260      * @return List<View> view prototypes with the given type name, or empty
261      *         list
262      */
263     public List<View> getViewsForType(ViewType viewTypeName) {
264         List<View> typeViews = new ArrayList<View>();
265 
266         // get view ids for the type
267         if (viewEntriesByType.containsKey(viewTypeName.name())) {
268             ViewTypeDictionaryIndex typeIndex = viewEntriesByType.get(viewTypeName.name());
269             for (Entry<String, String> typeEntry : typeIndex.getViewIndex().entrySet()) {
270                 View typeView = ddBeans.getBean(typeEntry.getValue(), View.class);
271                 typeViews.add(typeView);
272             }
273         } else {
274             throw new DataDictionaryException("Unable to find view index for type: " + viewTypeName);
275         }
276 
277         return typeViews;
278     }
279 
280     /**
281      * Initializes the view index {@code Map} then iterates through all the
282      * beans in the factory that implement {@code View}, adding them to the
283      * index
284      */
285     protected void buildViewIndicies() {
286         LOG.info("Starting View Index Building");
287 
288         viewBeanEntriesById = new HashMap<String, String>();
289         viewEntriesByType = new HashMap<String, ViewTypeDictionaryIndex>();
290 
291         String[] beanNames = ddBeans.getBeanNamesForType(View.class);
292         for (String beanName : beanNames) {
293             BeanDefinition beanDefinition = ddBeans.getMergedBeanDefinition(beanName);
294             PropertyValues propertyValues = beanDefinition.getPropertyValues();
295 
296             String id = ViewModelUtils.getStringValFromPVs(propertyValues, "id");
297             if (StringUtils.isBlank(id)) {
298                 id = beanName;
299             }
300 
301             if (viewBeanEntriesById.containsKey(id)) {
302                 throw new DataDictionaryException("Two views must not share the same id. Found duplicate id: " + id);
303             }
304 
305             viewBeanEntriesById.put(id, beanName);
306 
307             indexViewForType(propertyValues, id);
308         }
309 
310         LOG.info("Completed View Index Building");
311     }
312 
313     /**
314      * Performs additional indexing based on the view type associated with the view instance. The
315      * {@code ViewTypeService} 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 id - 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} 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 }