Commit da5ab9174351eedf2b616ddbf0867e500c9a71c7

Authored by Danilo Costa Viana
1 parent fa5140b5

Implementação de solução para leak de memória em aplicações usando beans

ViewScoped.
Showing 19 changed files with 162 additions and 60 deletions   Show diff stats
archetype/jsf-jpa/src/main/resources/archetype-resources/src/main/webapp/WEB-INF/web.xml
... ... @@ -10,11 +10,17 @@
10 10 </listener>
11 11 -->
12 12  
13   - <!-- If you are using servlet 2.5 then uncomment this listener -->
  13 + <!--
  14 + If you are using servlet 2.5 then uncomment those listeners and filters.
  15 + They're added automatically on servlet 3.0 or greater.
  16 + -->
14 17 <!--
15 18 <listener>
16 19 <listener-class>br.gov.frameworkdemoiselle.util.ServletListener</listener-class>
17 20 </listener>
  21 + <listener>
  22 + <listener-class>br.gov.frameworkdemoiselle.util.SessionListener</listener-class>
  23 + </listener>
18 24 <filter>
19 25 <filter-name>Demoiselle Servlet Filter</filter-name>
20 26 <filter-class>br.gov.frameworkdemoiselle.util.ServletFilter</filter-class>
... ...
impl/extension/jsf/src/main/java/br/gov/frameworkdemoiselle/internal/context/FacesViewBeanStore.java 0 → 100644
... ... @@ -0,0 +1,51 @@
  1 +package br.gov.frameworkdemoiselle.internal.context;
  2 +
  3 +import java.io.Serializable;
  4 +
  5 +import javax.enterprise.context.spi.Contextual;
  6 +import javax.enterprise.context.spi.CreationalContext;
  7 +import javax.enterprise.inject.Alternative;
  8 +
  9 +/**
  10 + * Store that keeps view scoped beans. It associates all view scoped beans with a view ID.
  11 + * When the ID changes (meaning the view changed) all old view scoped beans are destroyed
  12 + * before new beans for the new view are created and stored.
  13 + *
  14 + * @author SERPRO
  15 + *
  16 + */
  17 +@Alternative
  18 +public class FacesViewBeanStore implements Serializable {
  19 +
  20 + private static final long serialVersionUID = -8265458933971929432L;
  21 +
  22 + private Long lastViewId = null;
  23 +
  24 + private BeanStore store;
  25 +
  26 + synchronized BeanStore getStore(Long viewId, AbstractCustomContext context) {
  27 + if (lastViewId == null || !lastViewId.equals(viewId)) {
  28 + clear(context);
  29 + lastViewId = viewId;
  30 + store = AbstractCustomContext.createStore();
  31 + }
  32 +
  33 + return store;
  34 + }
  35 +
  36 + @SuppressWarnings({ "rawtypes", "unchecked" })
  37 + public void clear(AbstractCustomContext context) {
  38 + if (store != null) {
  39 + for (String id : store) {
  40 + Contextual contextual = context.getContextualStore().getContextual(id);
  41 + Object instance = store.getInstance(id);
  42 + CreationalContext creationalContext = store.getCreationalContext(id);
  43 +
  44 + if (contextual != null && instance != null) {
  45 + contextual.destroy(instance, creationalContext);
  46 + }
  47 + }
  48 + store.clear();
  49 + }
  50 + }
  51 +}
... ...
impl/extension/jsf/src/main/java/br/gov/frameworkdemoiselle/internal/context/FacesViewContextImpl.java
... ... @@ -39,17 +39,20 @@ package br.gov.frameworkdemoiselle.internal.context;
39 39 import java.util.concurrent.ConcurrentHashMap;
40 40 import java.util.concurrent.atomic.AtomicLong;
41 41  
  42 +import javax.enterprise.context.ApplicationScoped;
  43 +import javax.enterprise.context.ContextNotActiveException;
  44 +import javax.enterprise.context.spi.Context;
42 45 import javax.enterprise.event.Observes;
43 46 import javax.enterprise.inject.Alternative;
44 47 import javax.faces.component.UIViewRoot;
45 48 import javax.faces.context.FacesContext;
46   -import javax.servlet.http.HttpServletRequest;
47 49 import javax.servlet.http.HttpSession;
48 50  
49 51 import br.gov.frameworkdemoiselle.annotation.Priority;
50 52 import br.gov.frameworkdemoiselle.annotation.ViewScoped;
51 53 import br.gov.frameworkdemoiselle.context.ViewContext;
52 54 import br.gov.frameworkdemoiselle.lifecycle.BeforeSessionDestroyed;
  55 +import br.gov.frameworkdemoiselle.util.Beans;
53 56 import br.gov.frameworkdemoiselle.util.Faces;
54 57  
55 58 /**
... ... @@ -67,7 +70,7 @@ public class FacesViewContextImpl extends AbstractCustomContext implements ViewC
67 70  
68 71 private final AtomicLong atomicLong = new AtomicLong();
69 72  
70   - private ConcurrentHashMap<String, SessionBeanStore> sessionBeanStore = new ConcurrentHashMap<String, SessionBeanStore>();
  73 + private ConcurrentHashMap<String, FacesViewBeanStore> sessionBeanStore = new ConcurrentHashMap<String, FacesViewBeanStore>();
71 74  
72 75 private static final String FACES_KEY = FacesViewContextImpl.class.getCanonicalName();
73 76  
... ... @@ -82,15 +85,22 @@ public class FacesViewContextImpl extends AbstractCustomContext implements ViewC
82 85  
83 86 @Override
84 87 protected BeanStore getStore() {
85   - final String sessionId = getSessionId();
86   -
87   - if (sessionId==null){
  88 + String sessionId = getSessionId();
  89 + if (sessionId == null){
88 90 return null;
89 91 }
90 92  
  93 + /*
  94 + * Tenta obter o viewID de forma não thread-safe por questões de performance.
  95 + * Se o viewID não existe entra em um trecho thread-safe para incrementa-lo, evitando
  96 + * conflito entre duas requests tentando incrementar esse número.
  97 + */
91 98 Long viewId = (Long)Faces.getViewMap().get(FACES_KEY);
92 99 if (viewId==null){
93 100 synchronized (this) {
  101 +
  102 + //Tenta obte-lo novamente, caso entre a primeira tentativa e o bloqueio
  103 + //da thread outra thread já tenha criado o número.
94 104 viewId = (Long)Faces.getViewMap().get(FACES_KEY);
95 105 if (viewId==null){
96 106 viewId = atomicLong.incrementAndGet();
... ... @@ -99,24 +109,29 @@ public class FacesViewContextImpl extends AbstractCustomContext implements ViewC
99 109 }
100 110 }
101 111  
102   - SessionBeanStore currentStore = sessionBeanStore.get(sessionId);
  112 + //A mesma técnica de bloqueio de thread acima é usada aqui para
  113 + //criar um SessionBeanStore caso o mesmo ainda não exista.
  114 + FacesViewBeanStore currentStore = sessionBeanStore.get(sessionId);
103 115 if (currentStore==null){
104 116 synchronized (this) {
105   - currentStore = (SessionBeanStore) sessionBeanStore.get(sessionId);
  117 + currentStore = (FacesViewBeanStore) sessionBeanStore.get(sessionId);
106 118 if (currentStore==null){
107   - currentStore = new SessionBeanStore();
  119 + currentStore = new FacesViewBeanStore();
108 120 sessionBeanStore.put(sessionId, currentStore);
109 121 }
110 122 }
111 123 }
112   -
  124 +
113 125 return currentStore.getStore(viewId, this);
114 126 }
115 127  
116   - protected void clearInvalidatedSession(@Observes BeforeSessionDestroyed event){
117   - String sessionId = event.getSessionId();
  128 + /*
  129 + * Called before the session is invalidated for that user.
  130 + * Destroys all view scoped beans stored on that session.
  131 + */
  132 + private void clearInvalidatedSession(String sessionId){
118 133 if (sessionId != null){
119   - final SessionBeanStore store = sessionBeanStore.get(sessionId);
  134 + final FacesViewBeanStore store = sessionBeanStore.get(sessionId);
120 135 if (store!=null){
121 136 store.clear(this);
122 137 sessionBeanStore.remove(sessionId);
... ... @@ -124,8 +139,40 @@ public class FacesViewContextImpl extends AbstractCustomContext implements ViewC
124 139 }
125 140 }
126 141  
  142 + /*
  143 + * Returns the current session ID. Creates a session if one doesn't exist. Returns NULL if the session can't be created.
  144 + */
127 145 private String getSessionId(){
128 146 final HttpSession session = (HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(true);
129 147 return session!=null ? session.getId() : null;
130 148 }
  149 +
  150 + /**
  151 + * Observes HTTP session lifecycle and notifies the ViewContext of session events (creation or destruction)
  152 + * so view scoped beans can be created or destroyed based on their underlying session scopes.
  153 + *
  154 + * @author SERPRO
  155 + *
  156 + */
  157 + @ApplicationScoped
  158 + protected static class FacesViewSessionListener {
  159 +
  160 + /**
  161 + * Called before the session is invalidated for that user.
  162 + * Destroys all view scoped beans stored on that session.
  163 + */
  164 + protected void clearInvalidatedSession(@Observes BeforeSessionDestroyed event){
  165 + String sessionId = event.getSessionId();
  166 + try{
  167 + Context context = Beans.getBeanManager().getContext(ViewScoped.class);
  168 + if ( FacesViewContextImpl.class.isInstance(context) ){
  169 + ((FacesViewContextImpl)context).clearInvalidatedSession(sessionId);
  170 + }
  171 + }
  172 + catch(ContextNotActiveException ce){
  173 + //Nada a fazer, contexto não está ativo.
  174 + }
  175 + }
  176 + }
131 177 }
  178 +
... ...
impl/extension/jsf/src/main/java/br/gov/frameworkdemoiselle/internal/context/SessionBeanStore.java
... ... @@ -1,43 +0,0 @@
1   -package br.gov.frameworkdemoiselle.internal.context;
2   -
3   -import java.io.Serializable;
4   -
5   -import javax.enterprise.context.SessionScoped;
6   -import javax.enterprise.context.spi.Contextual;
7   -import javax.enterprise.context.spi.CreationalContext;
8   -
9   -@SessionScoped
10   -public class SessionBeanStore implements Serializable {
11   -
12   - private static final long serialVersionUID = -8265458933971929432L;
13   -
14   - private Long lastViewId = null;
15   -
16   - private BeanStore store;
17   -
18   - synchronized BeanStore getStore(Long viewId, AbstractCustomContext context) {
19   - if (lastViewId == null || !lastViewId.equals(viewId)) {
20   - clear(context);
21   - lastViewId = viewId;
22   - store = AbstractCustomContext.createStore();
23   - }
24   -
25   - return store;
26   - }
27   -
28   - @SuppressWarnings({ "rawtypes", "unchecked" })
29   - public void clear(AbstractCustomContext context) {
30   - if (store != null) {
31   - for (String id : store) {
32   - Contextual contextual = context.getContextualStore().getContextual(id);
33   - Object instance = store.getInstance(id);
34   - CreationalContext creationalContext = store.getCreationalContext(id);
35   -
36   - if (contextual != null && instance != null) {
37   - contextual.destroy(instance, creationalContext);
38   - }
39   - }
40   - store.clear();
41   - }
42   - }
43   -}
impl/extension/jsf/src/test/resources/exception-handler-authentication/web.xml
... ... @@ -40,6 +40,9 @@
40 40 <listener>
41 41 <listener-class>br.gov.frameworkdemoiselle.util.ServletListener</listener-class>
42 42 </listener>
  43 + <listener>
  44 + <listener-class>br.gov.frameworkdemoiselle.util.SessionListener</listener-class>
  45 + </listener>
43 46 <filter>
44 47 <filter-name>Demoiselle Servlet Filter</filter-name>
45 48 <filter-class>br.gov.frameworkdemoiselle.util.ServletFilter</filter-class>
... ...
impl/extension/jsf/src/test/resources/exception-handler-authorization/web.xml
... ... @@ -40,6 +40,9 @@
40 40 <listener>
41 41 <listener-class>br.gov.frameworkdemoiselle.util.ServletListener</listener-class>
42 42 </listener>
  43 + <listener>
  44 + <listener-class>br.gov.frameworkdemoiselle.util.SessionListener</listener-class>
  45 + </listener>
43 46 <filter>
44 47 <filter-name>Demoiselle Servlet Filter</filter-name>
45 48 <filter-class>br.gov.frameworkdemoiselle.util.ServletFilter</filter-class>
... ...
impl/extension/jsf/src/test/resources/exception-handler-config-compatibility/web.xml
... ... @@ -40,6 +40,9 @@
40 40 <listener>
41 41 <listener-class>br.gov.frameworkdemoiselle.util.ServletListener</listener-class>
42 42 </listener>
  43 + <listener>
  44 + <listener-class>br.gov.frameworkdemoiselle.util.SessionListener</listener-class>
  45 + </listener>
43 46 <filter>
44 47 <filter-name>Demoiselle Servlet Filter</filter-name>
45 48 <filter-class>br.gov.frameworkdemoiselle.util.ServletFilter</filter-class>
... ...
impl/extension/jsf/src/test/resources/exception-handler-config/web.xml
... ... @@ -40,6 +40,9 @@
40 40 <listener>
41 41 <listener-class>br.gov.frameworkdemoiselle.util.ServletListener</listener-class>
42 42 </listener>
  43 + <listener>
  44 + <listener-class>br.gov.frameworkdemoiselle.util.SessionListener</listener-class>
  45 + </listener>
43 46 <filter>
44 47 <filter-name>Demoiselle Servlet Filter</filter-name>
45 48 <filter-class>br.gov.frameworkdemoiselle.util.ServletFilter</filter-class>
... ...
impl/extension/jsf/src/test/resources/exception-handler-redirect-config-compatibility/web.xml
... ... @@ -40,6 +40,9 @@
40 40 <listener>
41 41 <listener-class>br.gov.frameworkdemoiselle.util.ServletListener</listener-class>
42 42 </listener>
  43 + <listener>
  44 + <listener-class>br.gov.frameworkdemoiselle.util.SessionListener</listener-class>
  45 + </listener>
43 46 <filter>
44 47 <filter-name>Demoiselle Servlet Filter</filter-name>
45 48 <filter-class>br.gov.frameworkdemoiselle.util.ServletFilter</filter-class>
... ...
impl/extension/jsf/src/test/resources/exception-handler-redirect-config/web.xml
... ... @@ -40,6 +40,9 @@
40 40 <listener>
41 41 <listener-class>br.gov.frameworkdemoiselle.util.ServletListener</listener-class>
42 42 </listener>
  43 + <listener>
  44 + <listener-class>br.gov.frameworkdemoiselle.util.SessionListener</listener-class>
  45 + </listener>
43 46 <filter>
44 47 <filter-name>Demoiselle Servlet Filter</filter-name>
45 48 <filter-class>br.gov.frameworkdemoiselle.util.ServletFilter</filter-class>
... ...
impl/extension/jsf/src/test/resources/exception-handler-redirect/web.xml
... ... @@ -40,6 +40,9 @@
40 40 <listener>
41 41 <listener-class>br.gov.frameworkdemoiselle.util.ServletListener</listener-class>
42 42 </listener>
  43 + <listener>
  44 + <listener-class>br.gov.frameworkdemoiselle.util.SessionListener</listener-class>
  45 + </listener>
43 46 <filter>
44 47 <filter-name>Demoiselle Servlet Filter</filter-name>
45 48 <filter-class>br.gov.frameworkdemoiselle.util.ServletFilter</filter-class>
... ...
impl/extension/jsf/src/test/resources/message/web.xml
... ... @@ -40,6 +40,9 @@
40 40 <listener>
41 41 <listener-class>br.gov.frameworkdemoiselle.util.ServletListener</listener-class>
42 42 </listener>
  43 + <listener>
  44 + <listener-class>br.gov.frameworkdemoiselle.util.SessionListener</listener-class>
  45 + </listener>
43 46 <filter>
44 47 <filter-name>Demoiselle Servlet Filter</filter-name>
45 48 <filter-class>br.gov.frameworkdemoiselle.util.ServletFilter</filter-class>
... ...
impl/extension/jsf/src/test/resources/proxy/web.xml
... ... @@ -40,6 +40,9 @@
40 40 <listener>
41 41 <listener-class>br.gov.frameworkdemoiselle.util.ServletListener</listener-class>
42 42 </listener>
  43 + <listener>
  44 + <listener-class>br.gov.frameworkdemoiselle.util.SessionListener</listener-class>
  45 + </listener>
43 46 <filter>
44 47 <filter-name>Demoiselle Servlet Filter</filter-name>
45 48 <filter-class>br.gov.frameworkdemoiselle.util.ServletFilter</filter-class>
... ...
impl/extension/servlet/src/main/java/br/gov/frameworkdemoiselle/util/SessionListener.java
... ... @@ -4,8 +4,8 @@ import javax.servlet.http.HttpSession;
4 4 import javax.servlet.http.HttpSessionEvent;
5 5 import javax.servlet.http.HttpSessionListener;
6 6  
7   -import br.gov.frameworkdemoiselle.lifecycle.BeforeSessionDestroyed;
8 7 import br.gov.frameworkdemoiselle.lifecycle.AfterSessionCreated;
  8 +import br.gov.frameworkdemoiselle.lifecycle.BeforeSessionDestroyed;
9 9  
10 10 /**
11 11 * <p>Implements the {@link HttpSessionListener} interface and fires two events.</p>
... ... @@ -19,14 +19,14 @@ import br.gov.frameworkdemoiselle.lifecycle.AfterSessionCreated;
19 19 *
20 20 */
21 21 public class SessionListener implements HttpSessionListener {
22   -
  22 +
23 23 @Override
24 24 public void sessionCreated(final HttpSessionEvent sessionEvent) {
25 25 Beans.getBeanManager().fireEvent(new AfterSessionCreated() {
26 26 @Override
27 27 public String getSessionId() {
28 28 HttpSession session = sessionEvent.getSession();
29   - return session != null ? session.getId() : null;
  29 + return session!=null ? session.getId() : null;
30 30 }
31 31 });
32 32 }
... ... @@ -37,7 +37,7 @@ public class SessionListener implements HttpSessionListener {
37 37 @Override
38 38 public String getSessionId() {
39 39 HttpSession session = sessionEvent.getSession();
40   - return session != null ? session.getId() : null;
  40 + return session!=null ? session.getId() : null;
41 41 }
42 42 });
43 43 }
... ...
impl/extension/servlet/src/test/java/test/Tests.java
... ... @@ -54,6 +54,7 @@ import br.gov.frameworkdemoiselle.security.ServletAuthorizer;
54 54 import br.gov.frameworkdemoiselle.util.BasicAuthFilter;
55 55 import br.gov.frameworkdemoiselle.util.ServletFilter;
56 56 import br.gov.frameworkdemoiselle.util.ServletListener;
  57 +import br.gov.frameworkdemoiselle.util.SessionListener;
57 58  
58 59 @Ignore
59 60 public final class Tests {
... ... @@ -76,6 +77,7 @@ public final class Tests {
76 77 .addClass(ServletAuthorizer.class)
77 78 .addClass(ServletFilter.class)
78 79 .addClass(ServletListener.class)
  80 + .addClass(SessionListener.class)
79 81 .addClass(HttpServletRequestProducer.class)
80 82 .addClass(HttpServletResponseProducer.class)
81 83 .addClass(HttpSessionProducer.class)
... ...
impl/extension/servlet/src/test/resources/producer/request/web.xml
... ... @@ -40,6 +40,9 @@
40 40 <listener>
41 41 <listener-class>br.gov.frameworkdemoiselle.util.ServletListener</listener-class>
42 42 </listener>
  43 + <listener>
  44 + <listener-class>br.gov.frameworkdemoiselle.util.SessionListener</listener-class>
  45 + </listener>
43 46  
44 47 <filter>
45 48 <filter-name>Demoiselle Servlet Filter</filter-name>
... ...
impl/extension/servlet/src/test/resources/producer/response/web.xml
... ... @@ -40,6 +40,9 @@
40 40 <listener>
41 41 <listener-class>br.gov.frameworkdemoiselle.util.ServletListener</listener-class>
42 42 </listener>
  43 + <listener>
  44 + <listener-class>br.gov.frameworkdemoiselle.util.SessionListener</listener-class>
  45 + </listener>
43 46  
44 47 <filter>
45 48 <filter-name>Demoiselle Servlet Filter</filter-name>
... ...
impl/extension/servlet/src/test/resources/security/authentication/basic/web.xml
... ... @@ -40,6 +40,9 @@
40 40 <listener>
41 41 <listener-class>br.gov.frameworkdemoiselle.util.ServletListener</listener-class>
42 42 </listener>
  43 + <listener>
  44 + <listener-class>br.gov.frameworkdemoiselle.util.SessionListener</listener-class>
  45 + </listener>
43 46  
44 47 <filter>
45 48 <filter-name>Demoiselle Servlet Filter</filter-name>
... ...
impl/extension/servlet/src/test/resources/security/authentication/form/web.xml
... ... @@ -40,6 +40,9 @@
40 40 <listener>
41 41 <listener-class>br.gov.frameworkdemoiselle.util.ServletListener</listener-class>
42 42 </listener>
  43 + <listener>
  44 + <listener-class>br.gov.frameworkdemoiselle.util.SessionListener</listener-class>
  45 + </listener>
43 46  
44 47 <filter>
45 48 <filter-name>Demoiselle Servlet Filter</filter-name>
... ...