View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.trinidad.bean;
20  
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.ArrayList;
25  
26  import java.util.Collection;
27  import java.util.Collections;
28  
29  import javax.faces.component.PartialStateHolder;
30  
31  import javax.faces.component.StateHolder;
32  import javax.faces.context.FacesContext;
33  
34  import org.apache.myfaces.trinidad.bean.util.StateUtils;
35  
36  /**
37   * Class for managing attached object on the component that are maintained as a Map of Lists
38   *
39   */
40  public class AttachedObjects<K, T> implements PartialStateHolder
41  {
42    /**
43     * Adds attached object to this collection. The key for the object does not have to be unique.
44     * @param key Object key
45     * @param obj Object value
46     */
47    final public void addAttachedObject(K key, T obj)
48    {    
49      List<T> objects = _objectMap.get(key);
50      if (objects == null)
51      {
52        objects = new ArrayList<T>(5);
53        _objectMap.put(key, objects);
54      }
55      
56      objects.add(obj);
57      
58      if (initialStateMarked())
59      {
60        // Clear initial state marker for the attached objects so that their full state is saved
61        clearInitialState();
62      }
63    }
64    /**
65     * Removes an object from this collection
66     * @param key Object key
67     * @param obj Object value
68     * @return true if the object fas found and removed, false otherwise
69     */
70    final public boolean removeAttachedObject(K key, T obj)
71    {
72      List<T> objects = _objectMap.get(key);
73      if (objects == null)
74      {
75        return false;
76      }
77      
78      boolean removed = objects.remove(obj);
79      if (removed)
80      {
81        if (initialStateMarked())
82        {
83          // Clear initial state marker for the attached objects so that their full state is saved
84          clearInitialState();
85        }
86      }
87      return removed;
88    }
89    
90    /**
91     * Retrieves a non-null immutable list of objects for this key from the collection.
92     * @param key Key value shared by all the objects in the List
93     * @return a list of objects for the given key
94     */
95    final public List<T> getAttachedObjectList(K key)
96    {
97      List<T> objects = _objectMap.get(key);
98      if (objects == null)
99      {
100       return Collections.emptyList();
101     }
102     return Collections.unmodifiableList(objects);
103   }
104   
105   /**
106    * Retreives a map of objects contained in this collection.
107    * @return a non-null immutable map of objects
108    */
109   final public Map<K, List<T>> getAttachedObjectMap()
110   {
111     if (_readOnlyObjectMap == null)
112     {
113       _readOnlyObjectMap = Collections.unmodifiableMap(_objectMap);
114     }
115     return _readOnlyObjectMap;
116   }
117         
118   @Override
119   public void markInitialState()
120   {
121     for (Map.Entry<K, List<T>> e : _objectMap.entrySet())
122     {
123       for (T obj : e.getValue())
124       {
125         if (obj instanceof PartialStateHolder)
126         {
127           ((PartialStateHolder)obj).markInitialState();
128         }
129       }
130     }
131     _initialStateMarked = true;
132   }
133   
134   @Override
135   public void clearInitialState()
136   {
137     _initialStateMarked = false;
138     for (Map.Entry<K, List<T>> e : _objectMap.entrySet())
139     {
140       for (T obj : e.getValue())
141       {
142         if (obj instanceof PartialStateHolder)
143         {
144           ((PartialStateHolder)obj).clearInitialState();
145         }
146       }
147     }
148   }
149   
150   @Override
151   public boolean initialStateMarked()
152   {
153     return _initialStateMarked;
154   }
155 
156   @Override
157   public Object saveState(
158     FacesContext facesContext)
159   {
160     Map<K, Object[]> state = new HashMap<K, Object[]>(_objectMap.size());
161     for (Map.Entry<K, List<T>> e : _objectMap.entrySet())
162     {
163       List<T> l = e.getValue();
164       Object[] entryState = new Object[l.size()];
165       boolean stateWasSaved = false;
166       for (int i = 0, size = entryState.length; i < size; ++i)
167       {
168         T obj = l.get(i);
169         if (_initialStateMarked)
170         {
171           // JSF 2 state saving, only save the attched object's state if it is a state holder,
172           // otherwise the re-application of the template will handle the re-creation of the
173           // object in the correct state
174           if (obj instanceof StateHolder)
175           {
176             entryState[i] = ((StateHolder)obj).saveState(facesContext);
177           }
178         }
179         else
180         {
181           // Use JSF <= 1.2 state saving method as the initial state was not marked
182           entryState[i] = StateUtils.saveStateHolder(facesContext, obj);
183         }
184 
185         stateWasSaved = (entryState[i] != null) ? true : stateWasSaved;
186       }
187 
188       if (stateWasSaved)
189       {
190         state.put(e.getKey(), entryState);
191       }
192     }
193     
194     Object [] savedState = null;
195     if (!state.isEmpty())
196     {
197     
198       // Record whether we used full state saving. This is necessary because methods like addClientBehvior() force
199       // full state saving under certain circumstances.
200       // To avoid using partial state restoration with the fully-saved state during restoreView(),  we will be using
201       // the saved flag instead of _initialStateMarked 
202       savedState = new Object[2];
203       savedState[0] = _initialStateMarked; 
204       savedState[1] = state;
205     }
206     return savedState;
207   }
208 
209   @Override
210   public void restoreState(
211     FacesContext facesContext,
212     Object       state)
213   {
214     if (state == null)
215       return;
216     
217     Object stateArray[] = (Object [])state;
218     boolean usePartialStateSaving = (Boolean)stateArray[0];
219     
220     @SuppressWarnings("unchecked")
221     Map<K, Object[]> savedState = (Map<K, Object[]>) stateArray[1];
222 
223     if (usePartialStateSaving)
224     {
225       // In JSF 2 state saving, we only need to super impose the state onto the existing
226       // attached object list of the current map as the attached objects will already be restored in
227       // the same order that they were in the previous request (if not there is an application
228       // bug).
229       for (Map.Entry<K, Object[]> e : savedState.entrySet())
230       {
231         // Assume that the objects were correctly re-attached to the component and we only
232         // need to restore the state onto the objects. The order must be maintained.
233         List<T> l = _objectMap.get(e.getKey());
234         Object[] entryState = e.getValue();
235         for (int i = 0, size = entryState.length; i < size; ++i)
236         {
237           if (entryState[i] != null)
238           {
239             T obj = l.get(i);
240             if (obj instanceof StateHolder)
241             {
242               ((StateHolder)obj).restoreState(facesContext, entryState[i]);
243             }
244           }
245         }
246       }
247     }
248     else
249     {
250       // For JSF <= 1.2 style state saving, we should ensure that we are empty and then
251       // re-hydrate the attched objects directly from the state
252       _objectMap.clear();
253 
254       for (Map.Entry<K, Object[]> e : savedState.entrySet())
255       {
256         Object[] entryState = e.getValue();
257         // Assume the list is not going to grow in this request, so only allocate the size
258         // of the list from the previous request
259         List<T> list = new ArrayList<T>(entryState.length);
260         for (int i = 0, size = entryState.length; i < size; ++i)
261         {
262           list.add((T)StateUtils.restoreStateHolder(facesContext, entryState[i]));
263         }
264 
265         _objectMap.put(e.getKey(), list);
266       }
267     }
268   }
269 
270   public boolean isTransient()
271   {
272     return _transient;
273   }
274 
275   public void setTransient(
276     boolean newTransientValue)
277   {
278     _transient = newTransientValue;
279   }
280 
281      
282   
283   private Map<K, List<T>> _objectMap = new HashMap<K, List<T>>(5, 1.0f);
284   
285   private Map<K, List<T>> _readOnlyObjectMap = null;
286   
287   private boolean _initialStateMarked = false;
288   private boolean _transient = false;
289 }