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.view.impl;
20  
21  import java.math.BigInteger;
22  import java.security.NoSuchAlgorithmException;
23  import java.security.SecureRandom;
24  import java.util.HashSet;
25  import java.util.Map;
26  import java.util.Set;
27  import java.util.concurrent.atomic.AtomicLong;
28  import javax.faces.context.ExternalContext;
29  import javax.faces.context.FacesContext;
30  import org.apache.myfaces.config.ManagedBeanDestroyer;
31  import org.apache.myfaces.config.RuntimeConfig;
32  import org.apache.myfaces.config.annotation.LifecycleProvider;
33  import org.apache.myfaces.config.annotation.LifecycleProviderFactory;
34  import org.apache.myfaces.shared.util.SubKeyMap;
35  import org.apache.myfaces.spi.ViewScopeProvider;
36  
37  /**
38   * Minimal implementation for view scope without CDI but always store
39   * the beans into session.
40   * 
41   * @author Leonardo Uribe
42   */
43  public class DefaultViewScopeHandler extends ViewScopeProvider
44  {
45      private static final String VIEW_SCOPE_PREFIX = "oam.view.SCOPE";
46      
47      private static final String VIEW_SCOPE_PREFIX_KEY = VIEW_SCOPE_PREFIX+".KEY";
48      
49      private static final String VIEW_SCOPE_PREFIX_MAP = VIEW_SCOPE_PREFIX+".MAP";
50      
51      static final char SEPARATOR_CHAR = '.';
52      
53      private final AtomicLong _count;
54      
55      private ManagedBeanDestroyer _mbDestroyer;
56      
57      public DefaultViewScopeHandler()
58      {
59          _count = new AtomicLong(_getSeed());
60      }
61      
62      /**
63       * Returns a cryptographically secure random number to use as the _count seed
64       */
65      private static long _getSeed()
66      {
67          SecureRandom rng;
68          try
69          {
70              // try SHA1 first
71              rng = SecureRandom.getInstance("SHA1PRNG");
72          }
73          catch (NoSuchAlgorithmException e)
74          {
75              // SHA1 not present, so try the default (which could potentially not be
76              // cryptographically secure)
77              rng = new SecureRandom();
78          }
79  
80          // use 48 bits for strength and fill them in
81          byte[] randomBytes = new byte[6];
82          rng.nextBytes(randomBytes);
83  
84          // convert to a long
85          return new BigInteger(randomBytes).longValue();
86      }
87      
88      /**
89       * Get the next token to be assigned to this request
90       * 
91       * @return
92       */
93      private String _getNextToken()
94      {
95          // atomically increment the value
96          long nextToken = _count.incrementAndGet();
97  
98          // convert using base 36 because it is a fast efficient subset of base-64
99          return Long.toString(nextToken, 36);
100     }
101     
102     @Override
103     public void onSessionDestroyed()
104     {
105         FacesContext facesContext = FacesContext.getCurrentInstance();
106         if (facesContext.getExternalContext().getSession(false) != null)
107         {
108             ExternalContext external = facesContext.getExternalContext();
109             Map<String, Object> sessionMap = external.getSessionMap();
110             String prefix = VIEW_SCOPE_PREFIX_MAP + SEPARATOR_CHAR;
111             Set<String> viewScopeIdSet = new HashSet<String>();
112             for (Map.Entry<String,Object> entry: sessionMap.entrySet())
113             {
114                 if (entry.getKey() != null && 
115                     entry.getKey().startsWith(prefix))
116                 {
117                     String viewScopeId = entry.getKey().substring(prefix.length(), 
118                             entry.getKey().indexOf(SEPARATOR_CHAR, prefix.length()));
119                     viewScopeIdSet.add(viewScopeId);
120                 }
121             }
122             if (!viewScopeIdSet.isEmpty())
123             {
124                 for (String viewScopeId : viewScopeIdSet )
125                 {
126                     this.destroyViewScopeMap(facesContext, viewScopeId);
127                 }
128             }
129         }
130     }
131     
132     @Override
133     public Map<String, Object> createViewScopeMap(FacesContext facesContext, String viewScopeId)
134     {
135         String fullToken = VIEW_SCOPE_PREFIX_MAP + SEPARATOR_CHAR + viewScopeId + SEPARATOR_CHAR;
136         Map<String, Object> map = _createSubKeyMap(facesContext, fullToken);
137         return map;
138     }
139     
140     @Override
141     public Map<String, Object> restoreViewScopeMap(FacesContext facesContext, String viewScopeId)
142     {
143         String fullToken = VIEW_SCOPE_PREFIX_MAP + SEPARATOR_CHAR + viewScopeId + SEPARATOR_CHAR;
144         Map<String, Object> map = _createSubKeyMap(facesContext, fullToken);
145         return map;
146     }
147     
148     private Map<String, Object> _createSubKeyMap(FacesContext context, String prefix)
149     {
150         ExternalContext external = context.getExternalContext();
151         Map<String, Object> sessionMap = external.getSessionMap();
152 
153         return new SubKeyMap<Object>(sessionMap, prefix);
154     }
155     
156     @Override
157     public String generateViewScopeId(FacesContext facesContext)
158     {
159         // To ensure uniqueness in this part we use a counter that 
160         // is stored into session and we add a random number to
161         // make difficult to guess the next number.
162         ExternalContext externalContext = facesContext.getExternalContext();
163         Object sessionObj = externalContext.getSession(true);
164         Integer sequence = null;
165         // synchronized to increase sequence if multiple requests
166         // are handled at the same time for the session
167         synchronized (sessionObj) 
168         {
169             Map<String, Object> map = externalContext.getSessionMap();
170             sequence = (Integer) map.get(VIEW_SCOPE_PREFIX_KEY);
171             if (sequence == null || sequence.intValue() == Integer.MAX_VALUE)
172             {
173                 sequence = Integer.valueOf(1);
174             }
175             else
176             {
177                 sequence = Integer.valueOf(sequence.intValue());
178             }
179             map.put(VIEW_SCOPE_PREFIX_KEY, sequence);
180         }
181         return _getNextToken()+'_'+sequence.toString();
182     }
183 
184     @Override
185     public void destroyViewScopeMap(FacesContext facesContext, String viewScopeId)
186     {
187         if (facesContext.getExternalContext().getSession(false) != null)
188         {        
189             String fullToken = VIEW_SCOPE_PREFIX_MAP + SEPARATOR_CHAR + viewScopeId + SEPARATOR_CHAR;
190             Map<String, Object> map = _createSubKeyMap(facesContext, fullToken);
191             
192             ManagedBeanDestroyer mbDestroyer = getManagedBeanDestroyer(facesContext.getExternalContext());
193             for (Map.Entry<String,Object> entry : map.entrySet())
194             {
195                 mbDestroyer.destroy(entry.getKey(), entry.getValue());
196             }
197             
198             map.clear();
199         }
200     }
201     
202     protected ManagedBeanDestroyer getManagedBeanDestroyer(ExternalContext externalContext)
203     {
204         if (_mbDestroyer == null)
205         {
206             RuntimeConfig runtimeConfig = RuntimeConfig.getCurrentInstance(externalContext);
207             LifecycleProvider lifecycleProvider = LifecycleProviderFactory
208                     .getLifecycleProviderFactory(externalContext).getLifecycleProvider(externalContext);
209 
210             _mbDestroyer = new ManagedBeanDestroyer(lifecycleProvider, runtimeConfig);
211         }
212         return _mbDestroyer;
213     }
214 }