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     public void onSessionDestroyed()
103     {
104         FacesContext facesContext = FacesContext.getCurrentInstance();
105         if (facesContext.getExternalContext().getSession(false) != null)
106         {
107             ExternalContext external = facesContext.getExternalContext();
108             Map<String, Object> sessionMap = external.getSessionMap();
109             String prefix = VIEW_SCOPE_PREFIX_MAP + SEPARATOR_CHAR;
110             Set<String> viewScopeIdSet = new HashSet<String>();
111             for (Map.Entry<String,Object> entry: sessionMap.entrySet())
112             {
113                 if (entry.getKey() != null && 
114                     entry.getKey().startsWith(prefix))
115                 {
116                     String viewScopeId = entry.getKey().substring(prefix.length(), 
117                             entry.getKey().indexOf(SEPARATOR_CHAR, prefix.length()));
118                     viewScopeIdSet.add(viewScopeId);
119                 }
120             }
121             if (!viewScopeIdSet.isEmpty())
122             {
123                 for (String viewScopeId : viewScopeIdSet )
124                 {
125                     this.destroyViewScopeMap(facesContext, viewScopeId);
126                 }
127             }
128         }
129     }
130     
131     public Map<String, Object> createViewScopeMap(FacesContext facesContext, String viewScopeId)
132     {
133         String fullToken = VIEW_SCOPE_PREFIX_MAP + SEPARATOR_CHAR + viewScopeId + SEPARATOR_CHAR;
134         Map<String, Object> map = _createSubKeyMap(facesContext, fullToken);
135         return map;
136     }
137     
138     public Map<String, Object> restoreViewScopeMap(FacesContext facesContext, String viewScopeId)
139     {
140         String fullToken = VIEW_SCOPE_PREFIX_MAP + SEPARATOR_CHAR + viewScopeId + SEPARATOR_CHAR;
141         Map<String, Object> map = _createSubKeyMap(facesContext, fullToken);
142         return map;
143     }
144     
145     private Map<String, Object> _createSubKeyMap(FacesContext context, String prefix)
146     {
147         ExternalContext external = context.getExternalContext();
148         Map<String, Object> sessionMap = external.getSessionMap();
149 
150         return new SubKeyMap<Object>(sessionMap, prefix);
151     }
152     
153     public String generateViewScopeId(FacesContext facesContext)
154     {
155         // To ensure uniqueness in this part we use a counter that 
156         // is stored into session and we add a random number to
157         // make difficult to guess the next number.
158         ExternalContext externalContext = facesContext.getExternalContext();
159         Object sessionObj = externalContext.getSession(true);
160         Integer sequence = null;
161         // synchronized to increase sequence if multiple requests
162         // are handled at the same time for the session
163         synchronized (sessionObj) 
164         {
165             Map<String, Object> map = externalContext.getSessionMap();
166             sequence = (Integer) map.get(VIEW_SCOPE_PREFIX_KEY);
167             if (sequence == null || sequence.intValue() == Integer.MAX_VALUE)
168             {
169                 sequence = Integer.valueOf(1);
170             }
171             else
172             {
173                 sequence = Integer.valueOf(sequence.intValue());
174             }
175             map.put(VIEW_SCOPE_PREFIX_KEY, sequence);
176         }
177         return _getNextToken()+'_'+sequence.toString();
178     }
179 
180     @Override
181     public void destroyViewScopeMap(FacesContext facesContext, String viewScopeId)
182     {
183         if (facesContext.getExternalContext().getSession(false) != null)
184         {        
185             String fullToken = VIEW_SCOPE_PREFIX_MAP + SEPARATOR_CHAR + viewScopeId + SEPARATOR_CHAR;
186             Map<String, Object> map = _createSubKeyMap(facesContext, fullToken);
187             
188             ManagedBeanDestroyer mbDestroyer = getManagedBeanDestroyer(facesContext.getExternalContext());
189             for (Map.Entry<String,Object> entry : map.entrySet())
190             {
191                 mbDestroyer.destroy(entry.getKey(), entry.getValue());
192             }
193             
194             map.clear();
195         }
196     }
197     
198     protected ManagedBeanDestroyer getManagedBeanDestroyer(ExternalContext externalContext)
199     {
200         if (_mbDestroyer == null)
201         {
202             RuntimeConfig runtimeConfig = RuntimeConfig.getCurrentInstance(externalContext);
203             LifecycleProvider lifecycleProvider = LifecycleProviderFactory
204                     .getLifecycleProviderFactory(externalContext).getLifecycleProvider(externalContext);
205 
206             _mbDestroyer = new ManagedBeanDestroyer(lifecycleProvider, runtimeConfig);
207         }
208         return _mbDestroyer;
209     }
210 }