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.application.viewstate;
20  
21  import java.io.Serializable;
22  import java.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.logging.Logger;
27  import javax.faces.context.FacesContext;
28  import org.apache.myfaces.shared.config.MyfacesConfig;
29  import org.apache.myfaces.spi.ViewScopeProvider;
30  import org.apache.myfaces.shared.util.LRULinkedHashMap;
31  
32  /**
33   *
34   */
35  class SerializedViewCollection implements Serializable
36  {
37      private static final Logger log = Logger.getLogger(SerializedViewCollection.class.getName());
38  
39      private static final Object[] EMPTY_STATES = new Object[]{null, null};
40  
41      private static final long serialVersionUID = -3734849062185115847L;
42      private final List<SerializedViewKey> _keys = 
43          new ArrayList<SerializedViewKey>(
44              MyfacesConfig.INIT_PARAM_NUMBER_OF_VIEWS_IN_SESSION_DEFAULT);
45      private final Map<SerializedViewKey, Object> _serializedViews = 
46          new HashMap<SerializedViewKey, Object>();
47      /**
48       * The viewScopeIds can be shared between multiple entries of the same
49       * view. To store it into session, the best is use two maps, one to 
50       * associate the view key with the view scope id and other to keep track 
51       * of the number of times the id is used. In that way it is possible to
52       * know when a view scope id has been discarded and destroy the view scope
53       * in the right time.
54       */
55      private HashMap<SerializedViewKey, String> _viewScopeIds = null;
56      private HashMap<String, Integer> _viewScopeIdCounts = null;
57  
58      private final Map<SerializedViewKey, SerializedViewKey> _precedence =
59          new HashMap<SerializedViewKey, SerializedViewKey>();
60      private Map<String, SerializedViewKey> _lastWindowKeys = null;
61  
62      public void put(FacesContext context, Object state, 
63          SerializedViewKey key, SerializedViewKey previousRestoredKey)
64      {
65          put(context, state, key, previousRestoredKey, null, null);
66      }
67      
68      public synchronized void put(FacesContext context, Object state, 
69          SerializedViewKey key, SerializedViewKey previousRestoredKey,
70          ViewScopeProvider viewScopeProvider, String viewScopeId)
71      {
72          if (state == null)
73          {
74              state = EMPTY_STATES;
75          }
76          else if (state instanceof Object[] &&
77              ((Object[])state).length == 2 &&
78              ((Object[])state)[0] == null &&
79              ((Object[])state)[1] == null)
80          {
81              // The generated state can be considered zero, set it as null
82              // into the map.
83              state = null;
84          }
85  
86          if (_serializedViews.containsKey(key))
87          {
88              // Update the state, the viewScopeId does not change.
89              _serializedViews.put(key, state);
90              // Make sure the view is at the end of the discard queue
91              while (_keys.remove(key))
92              {
93                  // do nothing
94              }
95              _keys.add(key);
96              return;
97          }
98  
99          Integer maxCount = getNumberOfSequentialViewsInSession(context);
100         if (maxCount != null)
101         {
102             if (previousRestoredKey != null)
103             {
104                 if (!_serializedViews.isEmpty())
105                 {
106                     _precedence.put((SerializedViewKey) key, previousRestoredKey);
107                 }
108                 else
109                 {
110                     // Note when the session is invalidated, _serializedViews map is empty,
111                     // but we could have a not null previousRestoredKey (the last one before
112                     // invalidate the session), so we need to check that condition before
113                     // set the precence. In that way, we ensure the precedence map will always
114                     // have valid keys.
115                     previousRestoredKey = null;
116                 }
117             }
118         }
119         _serializedViews.put(key, state);
120         
121         if (viewScopeProvider != null && viewScopeId != null)
122         {
123             if (_viewScopeIds == null)
124             {
125                 _viewScopeIds = new HashMap<SerializedViewKey, String>();
126             }
127             _viewScopeIds.put(key, viewScopeId);
128             if (_viewScopeIdCounts == null)
129             {
130                 _viewScopeIdCounts = new HashMap<String, Integer>();
131             }
132             Integer vscount = _viewScopeIdCounts.get(viewScopeId);
133             vscount = (vscount == null) ? 1 : vscount + 1;
134             _viewScopeIdCounts.put(viewScopeId, vscount);
135         }
136 
137         while (_keys.remove(key))
138         {
139             // do nothing
140         }
141         _keys.add(key);
142 
143         if (previousRestoredKey != null && maxCount != null && maxCount > 0)
144         {
145             int count = 0;
146             SerializedViewKey previousKey = (SerializedViewKey) key;
147             do
148             {
149                 previousKey = _precedence.get(previousKey);
150                 count++;
151             }
152             while (previousKey != null && count < maxCount);
153 
154             if (previousKey != null)
155             {
156                 SerializedViewKey keyToRemove = (SerializedViewKey) previousKey;
157                 // In theory it should be only one key but just to be sure
158                 // do it in a loop, but in this case if cache old views is on,
159                 // put on that map.
160                 do
161                 {
162                     while (_keys.remove(keyToRemove))
163                     {
164                         // do nothing
165                     }
166 
167                     _serializedViews.remove(keyToRemove);
168                     
169                     if (viewScopeProvider != null && _viewScopeIds != null)
170                     {
171                         String oldViewScopeId = _viewScopeIds.remove(keyToRemove);
172                         if (oldViewScopeId != null)
173                         {
174                             Integer vscount = _viewScopeIdCounts.get(oldViewScopeId);
175                             vscount = vscount - 1;
176                             if (vscount != null && vscount.intValue() < 1)
177                             {
178                                 _viewScopeIdCounts.remove(oldViewScopeId);
179                                 viewScopeProvider.destroyViewScopeMap(context, oldViewScopeId);
180                             }
181                             else
182                             {
183                                 _viewScopeIdCounts.put(oldViewScopeId, vscount);
184                             }
185                         }
186                     }
187 
188                     keyToRemove = _precedence.remove(keyToRemove);
189                 }
190                 while (keyToRemove != null);
191             }
192         }
193         int views = getNumberOfViewsInSession(context);
194         while (_keys.size() > views)
195         {
196             key = _keys.remove(0);
197             if (maxCount != null && maxCount > 0)
198             {
199                 SerializedViewKey keyToRemove = (SerializedViewKey) key;
200                 // Note in this case the key to delete is the oldest one,
201                 // so it could be at least one precedence, but to be safe
202                 // do it with a loop.
203                 do
204                 {
205                     keyToRemove = _precedence.remove(keyToRemove);
206                 }
207                 while (keyToRemove != null);
208             }
209 
210             _serializedViews.remove(key);
211             
212             if (viewScopeProvider != null && _viewScopeIds != null)
213             {
214                 String oldViewScopeId = _viewScopeIds.remove(key);
215                 if (oldViewScopeId != null)
216                 {
217                     Integer vscount = _viewScopeIdCounts.get(oldViewScopeId);
218                     vscount = vscount - 1;
219                     if (vscount != null && vscount.intValue() < 1)
220                     {
221                         _viewScopeIdCounts.remove(oldViewScopeId);
222                         viewScopeProvider.destroyViewScopeMap(context, oldViewScopeId);
223                     }
224                     else
225                     {
226                         _viewScopeIdCounts.put(oldViewScopeId, vscount);
227                     }
228                 }
229             }
230         }
231     }
232 
233     protected Integer getNumberOfSequentialViewsInSession(FacesContext context)
234     {
235         return MyfacesConfig.getCurrentInstance(context.getExternalContext()).getNumberOfSequentialViewsInSession();
236     }
237 
238     /**
239      * Reads the amount (default = 20) of views to be stored in session.
240      * @see ServerSideStateCacheImpl#NUMBER_OF_VIEWS_IN_SESSION_PARAM
241      * @param context FacesContext for the current request, we are processing
242      * @return Number vf views stored in the session
243      */
244     protected int getNumberOfViewsInSession(FacesContext context)
245     {
246         return MyfacesConfig.getCurrentInstance(context.getExternalContext()).getNumberOfViewsInSession();
247     }
248 
249     public synchronized void putLastWindowKey(FacesContext context, String id, SerializedViewKey key)
250     {
251         if (_lastWindowKeys == null)
252         {
253             Integer i = getNumberOfSequentialViewsInSession(context);
254             int j = getNumberOfViewsInSession(context);
255             if (i != null && i> 0)
256             {
257                 _lastWindowKeys = new LRULinkedHashMap<>((j / i) + 1);
258             }
259             else
260             {
261                 _lastWindowKeys = new LRULinkedHashMap(j + 1);
262             }
263         }
264         _lastWindowKeys.put(id, key);
265     }
266 
267     public SerializedViewKey getLastWindowKey(FacesContext context, String id)
268     {
269         if (_lastWindowKeys != null)
270         {
271             return _lastWindowKeys.get(id);
272         }
273         return null;
274     }
275 
276     public Object get(SerializedViewKey key)
277     {
278         Object value = _serializedViews.get(key);
279         if (value == null)
280         {
281             if (_serializedViews.containsKey(key))
282             {
283                 return EMPTY_STATES;
284             }
285         }
286         else if (value instanceof Object[] &&
287             ((Object[])value).length == 2 &&
288             ((Object[])value)[0] == null &&
289             ((Object[])value)[1] == null)
290         {
291             // Remember inside the state map null is stored as an empty array.
292             return null;
293         }
294         return value;
295     }
296 }