View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with this
4    * work for additional information regarding copyright ownership. The ASF
5    * licenses this file to you under the Apache License, Version 2.0 (the
6    * "License"); you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
9    * or agreed to in writing, software distributed under the License is
10   * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11   * KIND, either express or implied. See the License for the specific language
12   * governing permissions and limitations under the License.
13   */
14  
15  /*
16   * Whatever is the right licence?
17   * 
18   * Copyright 2009, Sebastian Prehn
19   *
20   * This file is part of JCR Blog
21   *
22   * JCR Blog is free software: you can redistribute it and/or modify
23   * it under the terms of the GNU General Public License as published by
24   * the Free Software Foundation, either version 3 of the License, or
25   * (at your option) any later version.
26   *
27   * JCR Blog is distributed in the hope that it will be useful,
28   * but WITHOUT ANY WARRANTY; without even the implied warranty of
29   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
30   * GNU General Public License for more details.
31   *
32   * You should have received a copy of the GNU General Public License
33   * along with JCR Blog.  If not, see <http://www.gnu.org/licenses/>.
34   *
35   *  Versioning:
36   *  $LastChangedDate$
37   *  $LastChangedRevision$
38   */
39  package org.jcr_blog.commons.jsf2cdi.scope;
40  
41  import java.lang.annotation.Annotation;
42  import java.util.Map;
43  import java.util.Map.Entry;
44  import java.util.concurrent.ConcurrentHashMap;
45  import javax.enterprise.context.ContextNotActiveException;
46  import javax.enterprise.context.spi.Context;
47  import javax.enterprise.context.spi.Contextual;
48  import javax.enterprise.context.spi.CreationalContext;
49  import javax.faces.component.UIViewRoot;
50  import javax.faces.context.FacesContext;
51  import javax.faces.event.PreDestroyViewMapEvent;
52  import javax.faces.event.SystemEvent;
53  import javax.faces.event.SystemEventListener;
54  
55  /**
56   * This class provides the contexts lifecycle for the new JSF-2 &#064;ViewScoped
57   * Context. Taken from Seam 3 faces project, for dependency declaration does
58   * only works for maven 3 currently. :-(
59   * TODO: include dependency when moving to maven 3.
60   *
61   * @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a>
62   * @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
63   * @author Sebastian Prehn <sebastian.prehn@planetswebdesign.de>
64   */
65  public class ViewContext implements Context, SystemEventListener {
66  
67      private final static String COMPONENT_MAP_NAME = "org.jboss.seam.faces.viewscope.componentInstanceMap";
68      private final static String CREATIONAL_MAP_NAME = "org.jboss.seam.faces.viewscope.creationalInstanceMap";
69  
70      private boolean isJsfSubscribed = false;
71  
72      @SuppressWarnings("unchecked")
73      @Override
74      public <T> T get(final Contextual<T> component) {
75          assertActive();
76  
77          if (!isJsfSubscribed) {
78              FacesContext.getCurrentInstance().getApplication().subscribeToEvent(PreDestroyViewMapEvent.class, this);
79              isJsfSubscribed = true;
80          }
81  
82          T instance = (T) getComponentInstanceMap().get(component);
83  
84          return instance;
85      }
86  
87      @SuppressWarnings("unchecked")
88      @Override
89      public <T> T get(final Contextual<T> component, final CreationalContext<T> creationalContext) {
90          assertActive();
91  
92          T instance = get(component);
93          if (instance == null && creationalContext != null) {
94                  Map<Contextual<?>, Object> componentInstanceMap = getComponentInstanceMap();
95                  Map<Contextual<?>, CreationalContext<?>> creationalContextMap = getCreationalInstanceMap();
96  
97                  synchronized (componentInstanceMap) {
98                      instance = (T) componentInstanceMap.get(component);
99                      if (instance == null) {
100                         instance = component.create(creationalContext);
101                         if (instance != null) {
102                             componentInstanceMap.put(component, instance);
103                             creationalContextMap.put(component, creationalContext);
104                         }
105                     }
106                 }
107         }
108 
109         return instance;
110     }
111 
112     /**
113      * Gets view map from view root.
114      * @return view map
115      */
116     private Map<String, Object> getViewMap() {
117         UIViewRoot viewRoot = getUIViewRoot();
118         if (viewRoot == null) {
119             return null;
120         }
121         return viewRoot.getViewMap();
122     }
123 
124     private UIViewRoot getUIViewRoot() {
125         FacesContext ctx = FacesContext.getCurrentInstance();
126         if (ctx == null) {
127             return null;
128         }
129         return ctx.getViewRoot();
130     }
131 
132     /**
133      *
134      * {@inheritDoc }
135      * 
136      * View scope is active whenever we have a view root.
137      *
138      */
139     @Override
140     public boolean isActive() {
141         return this.getUIViewRoot() != null;
142     }
143 
144     /**
145      * Asserts that context is active. Otherwise an exception will be thrown as
146      * required by {@link javax.enterprise.context.spi.Context#get(javax.enterprise.context.spi.Contextual) }
147      * and {@link javax.enterprise.context.spi.Context#get(javax.enterprise.context.spi.Contextual, javax.enterprise.context.spi.CreationalContext) }
148      * 
149      * @throws ContextNotActiveException if the context is not active
150      */
151     private void assertActive() {
152         if (!isActive()) {
153             throw new ContextNotActiveException("Context @ViewScoped is not active.");
154         }
155     }
156 
157     private Map<Contextual<?>, Object> getComponentInstanceMap() {
158         Map<String, Object> viewMap = getViewMap();
159         Map<Contextual<?>, Object> map = (ConcurrentHashMap<Contextual<?>, Object>) viewMap.get(COMPONENT_MAP_NAME);
160 
161         if (map == null) {
162             map = new ConcurrentHashMap<Contextual<?>, Object>();
163             viewMap.put(COMPONENT_MAP_NAME, map);
164         }
165 
166         return map;
167     }
168 
169     private Map<Contextual<?>, CreationalContext<?>> getCreationalInstanceMap() {
170         Map<String, Object> viewMap = getViewMap();
171         Map<Contextual<?>, CreationalContext<?>> map = (Map<Contextual<?>, CreationalContext<?>>) viewMap.get(CREATIONAL_MAP_NAME);
172 
173         if (map == null) {
174             map = new ConcurrentHashMap<Contextual<?>, CreationalContext<?>>();
175             viewMap.put(CREATIONAL_MAP_NAME, map);
176         }
177 
178         return map;
179     }
180 
181     @Override
182     public Class<? extends Annotation> getScope() {
183         return ViewScoped.class;
184     }
185 
186     ///////////////////////////////
187     // JSF Lifecycle Listener stuff
188     ///////////////////////////////
189     @Override
190     public boolean isListenerForSource(final Object source) {
191         if (source instanceof UIViewRoot) {
192             return true;
193         }
194 
195         return false;
196     }
197 
198     /**
199      * We get PreDestroyViewMapEvent events from the JSF servlet and destroy our
200      * contextual instances. This should (theoretically!) also get fired if the
201      * webapp closes, so there should be no need to manually track all view
202      * scopes and destroy them at a shutdown.
203      *
204      * @see javax.faces.event.SystemEventListener#processEvent(javax.faces.event.SystemEvent)
205      */
206     @SuppressWarnings("unchecked")
207     @Override
208     public void processEvent(final SystemEvent event) {
209         if (event instanceof PreDestroyViewMapEvent) {
210             Map<Contextual<?>, Object> componentInstanceMap = getComponentInstanceMap();
211             Map<Contextual<?>, CreationalContext<?>> creationalContextMap = getCreationalInstanceMap();
212 
213             if (componentInstanceMap != null) {
214                 for (Entry<Contextual<?>, Object> componentEntry : componentInstanceMap.entrySet()) {
215                     /*
216                      * No way to inform the compiler of type <T> information, so it
217                      * has to be abandoned here :(
218                      */
219                     Contextual contextual = componentEntry.getKey();
220                     Object instance = componentEntry.getValue();
221                     CreationalContext creational = creationalContextMap.get(contextual);
222 
223                     contextual.destroy(instance, creational);
224                 }
225             }
226         }
227     }
228 }