You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by ta...@apache.org on 2020/11/16 14:56:04 UTC

[myfaces] branch master updated: Faces 4.0: Provide ClientWindowScoped (Spec issue #1509)

This is an automated email from the ASF dual-hosted git repository.

tandraschko pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/myfaces.git


The following commit(s) were added to refs/heads/master by this push:
     new 4a94d27  Faces 4.0: Provide ClientWindowScoped (Spec issue #1509)
4a94d27 is described below

commit 4a94d27a2da91692b7ba12c66146a6cc0ce2b17f
Author: Thomas Andraschko <ta...@apache.org>
AuthorDate: Mon Nov 16 15:55:54 2020 +0100

    Faces 4.0: Provide ClientWindowScoped (Spec issue #1509)
---
 .../faces/lifecycle/ClientWindowScoped.java        |  45 ++++
 .../apache/myfaces/cdi/JsfArtifactProducer.java    |   8 +
 .../cdi/clientwindow/ClientWindowScopeContext.java | 282 +++++++++++++++++++++
 .../clientwindow/ClientWindowScopeExtension.java   |  42 +++
 .../jakarta.enterprise.inject.spi.Extension        |   1 +
 5 files changed, 378 insertions(+)

diff --git a/api/src/main/java/jakarta/faces/lifecycle/ClientWindowScoped.java b/api/src/main/java/jakarta/faces/lifecycle/ClientWindowScoped.java
new file mode 100644
index 0000000..e4f6042
--- /dev/null
+++ b/api/src/main/java/jakarta/faces/lifecycle/ClientWindowScoped.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package jakarta.faces.lifecycle;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import jakarta.enterprise.context.NormalScope;
+
+/**
+ * ClientWindowScoped is a CDI scope that causes the runtime to consider classes
+ * with this annotation to be in the scope of the current {@link ClientWindow}. The implementation must provide an
+ * implementation of {@code jakarta.enterprise.inject.spi.Extension} that implements the semantics such that beans with
+ * this annotation are created lazily, when referenced and accessed.
+ *
+ * @since 4.0
+ */
+@NormalScope(passivating = true)
+@Inherited
+@Documented
+@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ClientWindowScoped
+{
+    
+}
diff --git a/impl/src/main/java/org/apache/myfaces/cdi/JsfArtifactProducer.java b/impl/src/main/java/org/apache/myfaces/cdi/JsfArtifactProducer.java
index feb8a50..50f3ee6 100644
--- a/impl/src/main/java/org/apache/myfaces/cdi/JsfArtifactProducer.java
+++ b/impl/src/main/java/org/apache/myfaces/cdi/JsfArtifactProducer.java
@@ -37,6 +37,7 @@ import jakarta.faces.component.UIViewRoot;
 import jakarta.faces.context.ExternalContext;
 import jakarta.faces.context.FacesContext;
 import jakarta.faces.context.Flash;
+import jakarta.faces.lifecycle.ClientWindow;
 import jakarta.inject.Named;
 import org.apache.myfaces.cdi.view.ViewTransientScoped;
 
@@ -213,4 +214,11 @@ public class JsfArtifactProducer
        return FacesContext.getCurrentInstance().getViewRoot().getViewMap();
    }
 
+   @Produces
+   @Named("clientWindow")
+   @FacesScoped 
+   public ClientWindow getClientWindow()
+   {
+      return FacesContext.getCurrentInstance().getExternalContext().getClientWindow();
+   }
 }
diff --git a/impl/src/main/java/org/apache/myfaces/cdi/clientwindow/ClientWindowScopeContext.java b/impl/src/main/java/org/apache/myfaces/cdi/clientwindow/ClientWindowScopeContext.java
new file mode 100644
index 0000000..b154dfd
--- /dev/null
+++ b/impl/src/main/java/org/apache/myfaces/cdi/clientwindow/ClientWindowScopeContext.java
@@ -0,0 +1,282 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.cdi.clientwindow;
+
+import java.lang.annotation.Annotation;
+import java.util.Map;
+import jakarta.enterprise.context.ContextNotActiveException;
+import jakarta.enterprise.context.spi.Context;
+import jakarta.enterprise.context.spi.Contextual;
+import jakarta.enterprise.context.spi.CreationalContext;
+import jakarta.enterprise.inject.Typed;
+import jakarta.enterprise.inject.spi.BeanManager;
+import jakarta.faces.context.FacesContext;
+import jakarta.faces.lifecycle.ClientWindowScoped;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.myfaces.cdi.util.ContextualInstanceInfo;
+import org.apache.myfaces.cdi.util.ContextualStorage;
+
+/**
+ * Minimal implementation of ClientWindowScope.
+ */
+@Typed()
+public class ClientWindowScopeContext implements Context
+{
+    public static final String CLIENT_WINDOW_SCOPE_MAP = "oam.CLIENT_WINDOW_SCOPE_MAP";
+
+    private BeanManager beanManager;
+    
+    public ClientWindowScopeContext(BeanManager beanManager)
+    {
+        this.beanManager = beanManager;
+    }
+
+    /**
+     * An implementation has to return the underlying storage which
+     * contains the items held in the Context.
+     *
+     * @param createIfNotExist whether a ContextualStorage shall get created if it doesn't yet exist.
+     * @param facesContext 
+     * 
+     * @return the underlying storage
+     */
+    protected ContextualStorage getContextualStorage(boolean createIfNotExist, FacesContext facesContext)
+    {
+        if (facesContext == null)
+        {
+            throw new ContextNotActiveException(this.getClass().getName() + ": no current active FacesContext");
+        }
+
+        Map<String, Object> sessionMap =
+                facesContext.getExternalContext().getSessionMap();
+        Map<String, ContextualStorage> contextualStorageMap =
+                (Map<String, ContextualStorage>) sessionMap.get(CLIENT_WINDOW_SCOPE_MAP);
+        if (contextualStorageMap == null)
+        {
+            if (!createIfNotExist)
+            {
+                return null;
+            }
+
+            contextualStorageMap = new ConcurrentHashMap<>();
+            sessionMap.put(CLIENT_WINDOW_SCOPE_MAP, contextualStorageMap);
+        }
+
+        String clientWindowId = getCurrentClientWindowId();
+        ContextualStorage contextualStorage = contextualStorageMap.get(clientWindowId);
+        if (contextualStorage == null)
+        {
+            if (!createIfNotExist)
+            {
+                return null;
+            }
+
+            contextualStorage = new ContextualStorage(beanManager, false);
+            contextualStorageMap.put(clientWindowId, contextualStorage);
+        }
+
+        return contextualStorage;
+    }
+
+    @Override
+    public Class<? extends Annotation> getScope()
+    {
+        return ClientWindowScoped.class;
+    }
+
+    @Override
+    public boolean isActive()
+    {
+        return isActive(FacesContext.getCurrentInstance());
+    }
+
+    public boolean isActive(FacesContext facesContext)
+    {
+        if (facesContext == null || facesContext.getExternalContext().getClientWindow() == null)
+        {
+            return false;
+        }
+
+        return true;
+    }
+    
+    protected String getCurrentClientWindowId()
+    {
+        return FacesContext.getCurrentInstance().getExternalContext().getClientWindow().getId();
+    }
+
+    @Override
+    public <T> T get(Contextual<T> bean)
+    {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+
+        checkActive(facesContext);
+
+        if (facesContext != null)
+        {
+            ContextualStorage storage = getContextualStorage(false, facesContext);
+            if (storage != null)
+            {
+                Map<Object, ContextualInstanceInfo<?>> contextMap = storage.getStorage();
+                ContextualInstanceInfo<?> contextualInstanceInfo = contextMap.get(storage.getBeanKey(bean));
+
+                if (contextualInstanceInfo != null)
+                {
+                    return (T) contextualInstanceInfo.getContextualInstance();
+                }
+            }
+        }
+        else
+        {
+            throw new IllegalStateException("FacesContext cannot be found when resolving bean " + bean.toString());
+        }
+        return null;
+    }
+
+    @Override
+    public <T> T get(Contextual<T> bean, CreationalContext<T> creationalContext)
+    {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        
+        checkActive(facesContext);
+
+        ContextualStorage storage = getContextualStorage(true, facesContext);
+
+        Map<Object, ContextualInstanceInfo<?>> contextMap = storage.getStorage();
+        ContextualInstanceInfo<?> contextualInstanceInfo = contextMap.get(storage.getBeanKey(bean));
+
+        if (contextualInstanceInfo != null)
+        {
+            @SuppressWarnings("unchecked")
+            final T instance = (T) contextualInstanceInfo.getContextualInstance();
+
+            if (instance != null)
+            {
+                return instance;
+            }
+        }
+
+        return storage.createContextualInstance(bean, creationalContext);
+    }
+
+    /**
+     * Destroy the Contextual Instance of the given Bean.
+     * @param bean dictates which bean shall get cleaned up
+     * @return <code>true</code> if the bean was destroyed, <code>false</code> if there was no such bean.
+     */
+    public boolean destroy(Contextual bean)
+    {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        ContextualStorage storage = getContextualStorage(false, facesContext);
+        if (storage == null)
+        {
+            return false;
+        }
+        ContextualInstanceInfo<?> contextualInstanceInfo = storage.getStorage().get(storage.getBeanKey(bean));
+
+        if (contextualInstanceInfo == null)
+        {
+            return false;
+        }
+
+        bean.destroy(contextualInstanceInfo.getContextualInstance(), contextualInstanceInfo.getCreationalContext());
+        return true;
+    }
+
+    /**
+     * Make sure that the context is really active.
+     *
+     * @param facesContext the current {@link FacesContext}.
+     * @throws ContextNotActiveException if there is no active context for the current thread.
+     */
+    protected void checkActive(FacesContext facesContext)
+    {
+        if (!isActive(facesContext))
+        {
+            throw new ContextNotActiveException("CDI context with scope annotation @"
+                + getScope().getName() + " is not active with respect to the current thread");
+        }
+    }
+
+    public static void destroyAllActive(FacesContext facesContext)
+    {
+        if (facesContext == null)
+        {
+            return;
+        }
+        
+        Map<String, Object> sessionMap =
+                facesContext.getExternalContext().getSessionMap();
+        Map<String, ContextualStorage> contextualStorageMap =
+                (Map<String, ContextualStorage>) sessionMap.get(CLIENT_WINDOW_SCOPE_MAP);
+        if (contextualStorageMap == null)
+        {
+            return;
+        }
+
+        Iterator<String> iterator = contextualStorageMap.keySet().iterator();
+        while (iterator.hasNext())
+        {
+            String clientWindowId = iterator.next();
+            iterator.remove();
+
+            ContextualStorage contextualStorage = contextualStorageMap.get(clientWindowId);
+            destroyAllActive(facesContext, contextualStorage);
+        }
+    }
+    
+    public static void destroyAllActive(FacesContext facesContext, String clientWindowId)
+    {
+        if (facesContext == null)
+        {
+            return;
+        }
+        
+        Map<String, Object> sessionMap =
+                facesContext.getExternalContext().getSessionMap();
+        Map<String, ContextualStorage> contextualStorageMap =
+                (Map<String, ContextualStorage>) sessionMap.get(CLIENT_WINDOW_SCOPE_MAP);
+        if (contextualStorageMap == null)
+        {
+            return;
+        }
+        
+        ContextualStorage contextualStorage = contextualStorageMap.remove(clientWindowId);
+        destroyAllActive(facesContext, contextualStorage);
+    }
+    
+    protected static void destroyAllActive(FacesContext facesContext, ContextualStorage contextualStorage)
+    {
+        if (contextualStorage == null)
+        {
+            return;
+        }
+        
+        Map<Object, ContextualInstanceInfo<?>> contextMap = contextualStorage.getStorage();
+        for (Map.Entry<Object, ContextualInstanceInfo<?>> entry : contextMap.entrySet())
+        {
+            Contextual bean = contextualStorage.getBean(entry.getKey());
+
+            ContextualInstanceInfo<?> contextualInstanceInfo = entry.getValue();
+            bean.destroy(contextualInstanceInfo.getContextualInstance(), 
+                contextualInstanceInfo.getCreationalContext());
+        }
+    }
+}
diff --git a/impl/src/main/java/org/apache/myfaces/cdi/clientwindow/ClientWindowScopeExtension.java b/impl/src/main/java/org/apache/myfaces/cdi/clientwindow/ClientWindowScopeExtension.java
new file mode 100644
index 0000000..7703474
--- /dev/null
+++ b/impl/src/main/java/org/apache/myfaces/cdi/clientwindow/ClientWindowScopeExtension.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.cdi.clientwindow;
+
+import jakarta.enterprise.event.Observes;
+import jakarta.enterprise.inject.spi.AfterBeanDiscovery;
+import jakarta.enterprise.inject.spi.BeanManager;
+import jakarta.enterprise.inject.spi.BeforeBeanDiscovery;
+import jakarta.enterprise.inject.spi.Extension;
+import jakarta.faces.lifecycle.ClientWindowScoped;
+
+public class ClientWindowScopeExtension implements Extension
+{
+    private ClientWindowScopeContext clientWindowScopedContext;
+
+    void beforeBeanDiscovery(@Observes BeforeBeanDiscovery event, BeanManager beanManager)
+    {
+        event.addScope(ClientWindowScoped.class, true, false);
+    }
+    
+    void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager)
+    {
+        clientWindowScopedContext = new ClientWindowScopeContext(beanManager);
+        afterBeanDiscovery.addContext(clientWindowScopedContext);
+    }
+}
diff --git a/impl/src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension b/impl/src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension
index 9ef7c01..76c09f6 100644
--- a/impl/src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension
+++ b/impl/src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension
@@ -10,3 +10,4 @@ org.apache.myfaces.config.annotation.CdiAnnotationProviderExtension
 org.apache.myfaces.push.cdi.PushContextCDIExtension
 org.apache.myfaces.flow.cdi.FlowBuilderExtension
 org.apache.myfaces.flow.cdi.FlowScopeExtension
+org.apache.myfaces.cdi.clientwindow.ClientWindowScopeExtension