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.uif.view;
17  
18  import java.io.Serializable;
19  import java.util.HashMap;
20  import java.util.HashSet;
21  import java.util.Map;
22  import java.util.Map.Entry;
23  import java.util.Set;
24  
25  import org.apache.commons.lang.StringUtils;
26  import org.apache.log4j.Logger;
27  import org.kuali.rice.krad.uif.component.Component;
28  import org.kuali.rice.krad.uif.container.CollectionGroup;
29  import org.kuali.rice.krad.uif.container.Container;
30  import org.kuali.rice.krad.uif.field.DataField;
31  import org.kuali.rice.krad.uif.util.ComponentUtils;
32  import org.kuali.rice.krad.uif.util.LifecycleElement;
33  import org.kuali.rice.krad.uif.util.ViewCleaner;
34  
35  /**
36   * Holds component indexes of a {@link View} instance for convenient retrieval during the
37   * lifecycle and persisting components for the refresh process.
38   *
39   * @author Kuali Rice Team (rice.collab@kuali.org)
40   */
41  public class ViewIndex implements Serializable {
42      private static final long serialVersionUID = 4700818801272201371L;
43      
44      private static final Logger LOG = Logger.getLogger(ViewIndex.class);
45  
46      private Map<String, Component> index;
47      private Map<String, DataField> dataFieldIndex;
48  
49      private Map<String, CollectionGroup> collectionsIndex;
50      private Map<String, LifecycleElement> lifecycleElementsByPath;
51  
52      private Set<String> idsToHoldInIndex;
53      private Set<String> assignedIds;
54  
55      /**
56       * Default Constructor.
57       */
58      public ViewIndex() {
59          index = new HashMap<String, Component>();
60          dataFieldIndex = new HashMap<String, DataField>();
61          collectionsIndex = new HashMap<String, CollectionGroup>();
62          lifecycleElementsByPath = new HashMap<String, LifecycleElement>();
63          idsToHoldInIndex = new HashSet<String>();
64          assignedIds = new HashSet<String>();
65      }
66  
67      /**
68       * Clears view indexes, for reinitializing indexes at the start of each phase.
69       */
70      protected void clearIndex(View view) {
71          index = new HashMap<String, Component>();
72          dataFieldIndex = new HashMap<String, DataField>();
73          collectionsIndex = new HashMap<String, CollectionGroup>();
74          lifecycleElementsByPath = new HashMap<String, LifecycleElement>();
75      }
76  
77      /**
78       * Adds an entry to the main index for the given component. If the component is of type
79       * <code>DataField</code> or <code>CollectionGroup</code> an entry is created in the
80       * corresponding indexes for those types as well. Then the #indexComponent method is called for
81       * each of the component's children
82       *
83       * <p>
84       * If the component is already contained in the indexes, it will be replaced
85       * </p>
86       *
87       * <p>
88       * <code>DataField</code> instances are indexed by the attribute path. This is useful for
89       * retrieving the InputField based on the incoming request parameter
90       * </p>
91       *
92       * <p>
93       * <code>CollectionGroup</code> instances are indexed by the collection path. This is useful for
94       * retrieving the CollectionGroup based on the incoming request parameter
95       * </p>
96       *
97       * @param component component instance to index
98       */
99      public void indexComponent(Component component) {
100         if (component == null) {
101             return;
102         }
103 
104         synchronized (index) {
105             index.put(component.getId(), component);
106         }
107 
108         synchronized (lifecycleElementsByPath) {
109             lifecycleElementsByPath.put(component.getViewPath(), component);
110         }
111 
112         if (component instanceof Container) {
113             Container container = (Container) component;
114             if (container.getLayoutManager() != null) {
115                 synchronized (lifecycleElementsByPath) {
116                     lifecycleElementsByPath.put(container.getLayoutManager().getViewPath(),
117                             container.getLayoutManager());
118                 }
119             }
120         }
121 
122         if (component instanceof DataField) {
123             DataField field = (DataField) component;
124 
125             synchronized (dataFieldIndex) {
126                 dataFieldIndex.put(field.getBindingInfo().getBindingPath(), field);
127             }
128         } else if (component instanceof CollectionGroup) {
129             CollectionGroup collectionGroup = (CollectionGroup) component;
130 
131             synchronized (collectionsIndex) {
132                 collectionsIndex.put(collectionGroup.getBindingInfo().getBindingPath(), collectionGroup);
133             }
134         }
135     }
136 
137     /**
138      * Invoked after the view lifecycle or component refresh has run to clear indexes that are not
139      * needed for the post.
140      */
141     public void clearIndexesAfterRender() {
142         // build list of ids for components whose final state needs to be kept
143         for (Component component : index.values()) {
144             if (component == null) {
145                 continue;
146             }
147 
148             if (component.isDisableSessionPersistence()) {
149                 continue;
150             }
151 
152             // if component is a collection we need to keep it for  add/delete and other collection functions
153             if (component.isForceSessionPersistence() || ComponentUtils.canBeRefreshed(component) ||
154                     (component instanceof CollectionGroup)) {
155                 synchronized (idsToHoldInIndex) {
156                     idsToHoldInIndex.add(component.getId());
157                 }
158             }
159         }
160 
161         Map<String, Component> holdComponentStates = new HashMap<String, Component>();
162         synchronized (index) {
163             for (Entry<String, Component> indexEntry : index.entrySet()) {
164                 if (idsToHoldInIndex.contains(indexEntry.getKey())) {
165                     Component component = indexEntry.getValue();
166 
167                     ViewCleaner.cleanComponent(component, this);
168                     holdComponentStates.put(indexEntry.getKey(), component);
169                 }
170             }
171         }
172         index = holdComponentStates;
173 
174         for (CollectionGroup collectionGroup : collectionsIndex.values()) {
175             ViewCleaner.cleanComponent(collectionGroup, this);
176         }
177 
178         dataFieldIndex.clear();
179         assignedIds.clear();
180     }
181 
182     /**
183      * Indicates whether the given component id is for a component maintained by the view index for
184      * the refresh process
185      *
186      * @param componentId id for the component to check
187      * @return true if the component id is for a refreshed component, false if not
188      */
189     public boolean isIdForRefreshComponent(String componentId) {
190         return idsToHoldInIndex != null && idsToHoldInIndex.contains(componentId);
191     }
192 
193     /**
194      * Retrieves a <code>Component</code> from the view index by Id
195      *
196      * @param id id for the component to retrieve
197      * @return Component instance found in index, or null if no such component exists
198      */
199     public Component getComponentById(String id) {
200         return index.get(id);
201     }
202 
203     /**
204      * Retrieves a <code>DataField</code> instance from the index
205      *
206      * @param propertyPath full path of the data field (from the form)
207      * @return DataField instance for the path or Null if not found
208      */
209     public DataField getDataFieldByPath(String propertyPath) {
210         return dataFieldIndex.get(propertyPath);
211     }
212 
213     /**
214      * Retrieves a <code>DataField</code> instance that has the given property name specified (note
215      * this is not the full binding path and first match is returned)
216      *
217      * @param propertyName property name for field to retrieve
218      * @return DataField instance found or null if not found
219      */
220     public DataField getDataFieldByPropertyName(String propertyName) {
221         DataField dataField = null;
222 
223         for (DataField field : dataFieldIndex.values()) {
224             if (StringUtils.equals(propertyName, field.getPropertyName())) {
225                 dataField = field;
226                 break;
227             }
228         }
229 
230         return dataField;
231     }
232 
233     /**
234      * Returns a set of all data field paths that will be displayed (rendered).
235      *
236      * @return Set<String> set of property paths for data fields
237      */
238     public Set<String> getAllDisplayedPropertyPaths() {
239         Set<String> propertyPaths = new HashSet<String>();
240 
241         for (DataField field : dataFieldIndex.values()) {
242             if (field.isRender()) {
243                 propertyPaths.add(field.getBindingInfo().getBindingPath());
244             }
245         }
246 
247         return propertyPaths;
248     }
249 
250     public Map<String, LifecycleElement> getLifecycleElements() {
251         return lifecycleElementsByPath;
252     }
253 
254     public LifecycleElement getLifecycleElementByPath(String path) {
255         if ((lifecycleElementsByPath != null) && lifecycleElementsByPath.containsKey(path)) {
256             return lifecycleElementsByPath.get(path);
257         }
258 
259         return null;
260     }
261 
262     /**
263      * Gets the Map that contains attribute field indexing information. The Map key points to an
264      * attribute binding path, and the Map value is the <code>DataField</code> instance
265      *
266      * @return data fields index map
267      */
268     public Map<String, DataField> getDataFieldIndex() {
269         return this.dataFieldIndex;
270     }
271 
272     /**
273      * Gets the Map that contains collection indexing information. The Map key gives the binding
274      * path to the collection, and the Map value givens the <code>CollectionGroup</code> instance
275      *
276      * @return collection index map
277      */
278     public Map<String, CollectionGroup> getCollectionsIndex() {
279         return this.collectionsIndex;
280     }
281 
282     /**
283      * Retrieves a <code>CollectionGroup</code> instance from the index
284      *
285      * @param collectionPath full path of the collection (from the form)
286      * @return CollectionGroup instance for the collection path or Null if not found
287      */
288     public CollectionGroup getCollectionGroupByPath(String collectionPath) {
289         return collectionsIndex.get(collectionPath);
290     }
291 
292     /**
293      * Observe an assigned ID.
294      *
295      * @param id The ID to observe.
296      * @return True if the ID is unique, false if the ID has already been observed.
297      */
298     public boolean observeAssignedId(String id) {
299         if (assignedIds.contains(id)) {
300             return false;
301         }
302 
303         synchronized (assignedIds) {
304             return assignedIds.add(id);
305         }
306     }
307 
308     /**
309      * Returns a clone of the view index.
310      *
311      * @return ViewIndex clone
312      */
313     public ViewIndex copy() {
314         ViewIndex viewIndexCopy = new ViewIndex();
315 
316         if (this.index != null) {
317             Map<String, Component> indexCopy = new HashMap<String, Component>();
318             for (Map.Entry<String, Component> indexEntry : this.index.entrySet()) {
319                 if (indexEntry.getValue() instanceof View) {
320                     LOG.warn("view reference at " + indexEntry);
321                 } else {
322                     indexCopy.put(indexEntry.getKey(), (Component) indexEntry.getValue().copy());
323                 }
324             }
325 
326             viewIndexCopy.index = indexCopy;
327         }
328 
329         if (this.dataFieldIndex != null) {
330             Map<String, DataField> dataFieldIndexCopy = new HashMap<String, DataField>();
331             for (Map.Entry<String, DataField> indexEntry : this.dataFieldIndex.entrySet()) {
332                 dataFieldIndexCopy.put(indexEntry.getKey(), (DataField) indexEntry.getValue().copy());
333             }
334 
335             viewIndexCopy.dataFieldIndex = dataFieldIndexCopy;
336         }
337 
338         if (this.collectionsIndex != null) {
339             Map<String, CollectionGroup> collectionsIndexCopy = new HashMap<String, CollectionGroup>();
340             for (Map.Entry<String, CollectionGroup> indexEntry : this.collectionsIndex.entrySet()) {
341                 collectionsIndexCopy.put(indexEntry.getKey(), (CollectionGroup) indexEntry.getValue().copy());
342             }
343 
344             viewIndexCopy.collectionsIndex = collectionsIndexCopy;
345         }
346 
347         return viewIndexCopy;
348     }
349 
350 }