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