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