You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by st...@apache.org on 2010/06/29 01:07:23 UTC

svn commit: r958778 - in /myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main: java/org/apache/myfaces/extensions/cdi/javaee/jsf/impl/scope/ java/org/apache/myfaces/extensions/cdi/javaee/jsf/impl/scope/view/ resources/META-INF/services/

Author: struberg
Date: Mon Jun 28 23:07:23 2010
New Revision: 958778

URL: http://svn.apache.org/viewvc?rev=958778&view=rev
Log:
EXTCDI-14 CDI support for @ViewScoped

Added:
    myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/javaee/jsf/impl/scope/
    myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/javaee/jsf/impl/scope/view/
    myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/javaee/jsf/impl/scope/view/MockViewScopedContext.java
    myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/javaee/jsf/impl/scope/view/ViewScopedContext.java
    myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/javaee/jsf/impl/scope/view/ViewScopedExtension.java
Modified:
    myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension

Added: myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/javaee/jsf/impl/scope/view/MockViewScopedContext.java
URL: http://svn.apache.org/viewvc/myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/javaee/jsf/impl/scope/view/MockViewScopedContext.java?rev=958778&view=auto
==============================================================================
--- myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/javaee/jsf/impl/scope/view/MockViewScopedContext.java (added)
+++ myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/javaee/jsf/impl/scope/view/MockViewScopedContext.java Mon Jun 28 23:07:23 2010
@@ -0,0 +1,69 @@
+/*
+ * 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.extensions.cdi.javaee.jsf.impl.scope.view;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+/**
+ * <p>This is a Mock version of the {@link ViewScopedContext}.
+ * It will automatically get used instead of it's parent class
+ * if we are in the
+ * {@link javax.faces.application.ProjectStage#UnitTest}.</p>
+ *
+ * <p>There is of course no automatic cleaning if we transit over to the
+ * next view! In this case you can use {@link #resetViewMap()} to clean
+ * the mock ViewMap manually in your unit test.</p>
+ *
+ * <p><b>Attention:</b> The ViewMap is a shared static ConcurrentHashMap,
+ * so this implementation is not able to emulate the behaviour of multiple views
+ * at the same time!</p>
+ */
+public class MockViewScopedContext extends ViewScopedContext
+{
+
+    private static Map<String, Object> mockViewMap = new ConcurrentHashMap<String, Object>();
+
+    /**
+     * Simply clear the mock ViewMap.
+     * This function should get called if the same unit test needs to test multiple
+     * views and also between different tests if the container doesn't get restarted
+     * in between.
+     */
+    public static void resetViewMap()
+    {
+        mockViewMap.clear();
+    }
+
+    @Override
+    protected Map<String, Object> getViewMap()
+    {
+        return mockViewMap;
+    }
+
+    /**
+     * @return always <code>true</code> since in a unit test it's always active.
+     */
+    @Override
+    public boolean isActive()
+    {
+        return true;
+    }
+}
\ No newline at end of file

Added: myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/javaee/jsf/impl/scope/view/ViewScopedContext.java
URL: http://svn.apache.org/viewvc/myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/javaee/jsf/impl/scope/view/ViewScopedContext.java?rev=958778&view=auto
==============================================================================
--- myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/javaee/jsf/impl/scope/view/ViewScopedContext.java (added)
+++ myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/javaee/jsf/impl/scope/view/ViewScopedContext.java Mon Jun 28 23:07:23 2010
@@ -0,0 +1,233 @@
+/*
+ * 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.extensions.cdi.javaee.jsf.impl.scope.view;
+
+import java.lang.annotation.Annotation;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.enterprise.context.ContextNotActiveException;
+import javax.enterprise.context.spi.Context;
+import javax.enterprise.context.spi.Contextual;
+import javax.enterprise.context.spi.CreationalContext;
+
+import javax.faces.bean.ViewScoped;
+import javax.faces.component.UIViewRoot;
+import javax.faces.context.FacesContext;
+import javax.faces.event.PreDestroyViewMapEvent;
+import javax.faces.event.SystemEvent;
+import javax.faces.event.SystemEventListener;
+
+/**
+ * This class provides the contexts lifecycle for the
+ * new JSF-2 &#064;ViewScoped Context
+ */
+public class ViewScopedContext implements Context, SystemEventListener
+{
+
+    private final static String COMPONENT_MAP_NAME ="codi.componentInstanceMap";
+    private final static String CREATIONAL_MAP_NAME ="codi.creationalInstanceMap";
+
+    private boolean isJsfSubscribed = false;
+
+
+    public <T> T get(Contextual<T> component)
+    {
+        checkActive();
+
+        if(!isJsfSubscribed)
+        {
+            FacesContext.getCurrentInstance().getApplication().subscribeToEvent(PreDestroyViewMapEvent.class, this);
+
+            isJsfSubscribed = true;
+        }
+
+        Map<String, Object> viewMap = getViewMap();
+
+        @SuppressWarnings("unchecked")
+        ConcurrentHashMap<Contextual<?>, Object> componentInstanceMap
+                = (ConcurrentHashMap<Contextual<?>, Object>) viewMap.get(COMPONENT_MAP_NAME);
+
+        if(componentInstanceMap == null)
+        {
+            return null;
+        }
+
+        @SuppressWarnings("unchecked")
+        T instance = (T) componentInstanceMap.get(component);
+
+        return instance;
+    }
+
+    public <T> T get(Contextual<T> component, CreationalContext<T> creationalContext)
+    {
+        checkActive();
+
+        Map<String, Object> viewMap = getViewMap();
+
+        @SuppressWarnings("unchecked")
+        ConcurrentHashMap<Contextual<?>, Object> componentInstanceMap
+                = (ConcurrentHashMap<Contextual<?>, Object>) viewMap.get(COMPONENT_MAP_NAME);
+
+        if(componentInstanceMap == null)
+        {
+            // TODO we now need to start being carefull with reentrancy...
+            componentInstanceMap = new ConcurrentHashMap<Contextual<?>, Object>();
+            viewMap.put(COMPONENT_MAP_NAME, componentInstanceMap);
+        }
+
+        @SuppressWarnings("unchecked")
+        ConcurrentHashMap<Contextual<?>, CreationalContext<?>> creationalContextMap
+                = (ConcurrentHashMap<Contextual<?>, CreationalContext<?>>) viewMap.get(CREATIONAL_MAP_NAME);
+        if(creationalContextMap == null)
+        {
+            // TODO we now need to start being carefull with reentrancy...
+            creationalContextMap = new ConcurrentHashMap<Contextual<?>, CreationalContext<?>>();
+            viewMap.put(CREATIONAL_MAP_NAME, creationalContextMap);
+        }
+
+        @SuppressWarnings("unchecked")
+        T instance = (T) componentInstanceMap.get(component);
+        if (instance != null)
+        {
+            return instance;
+        }
+
+        if(creationalContext == null)
+        {
+            return null;
+        }
+
+        synchronized (componentInstanceMap)
+        {
+            // just to make sure...
+            @SuppressWarnings("unchecked")
+            T i = (T)componentInstanceMap.get(component);
+            if (i != null)
+            {
+                return i;
+            }
+
+            instance = component.create(creationalContext);
+
+            if (instance != null)
+            {
+                componentInstanceMap.put(component, instance);
+                creationalContextMap.put(component, creationalContext);
+            }
+        }
+
+        return  instance;
+    }
+
+    public Class<? extends Annotation> getScope()
+    {
+        return ViewScoped.class;
+    }
+
+    /**
+     * The view context is active if a valid ViewRoot could be detected.
+     */
+    public boolean isActive()
+    {
+        return getViewRoot() != null;
+    }
+
+    private void checkActive()
+    {
+        if (!isActive())
+        {
+            throw new ContextNotActiveException("WebBeans context with scope annotation " +
+                                                "@ViewScoped is not active with respect to the current thread");
+        }
+    }
+
+    public boolean isListenerForSource(Object source)
+    {
+        if (source instanceof UIViewRoot)
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * We get PreDestroyViewMapEvent events from the JSF servlet and destroy our contextual
+     * instances. This should (theoretically!) also get fired if the webapp closes, so there
+     * should be no need to manually track all view scopes and destroy them at a shutdown.
+     *
+     * @see javax.faces.event.SystemEventListener#processEvent(javax.faces.event.SystemEvent)
+     */
+    @SuppressWarnings("unchecked")
+    public void processEvent(SystemEvent event)
+    {
+        if (event instanceof PreDestroyViewMapEvent)
+        {
+            // better use the viewmap we get from the event to prevent concurrent modification problems
+            Map<String, Object> viewMap = ((UIViewRoot) event.getSource()).getViewMap();
+
+            ConcurrentHashMap<Contextual<?>, Object> componentInstanceMap
+                    = (ConcurrentHashMap<Contextual<?>, Object>) viewMap.get(COMPONENT_MAP_NAME);
+
+            ConcurrentHashMap<Contextual<?>, CreationalContext<?>> creationalContextMap
+                    = (ConcurrentHashMap<Contextual<?>, CreationalContext<?>>) viewMap.get(CREATIONAL_MAP_NAME);
+
+            if(componentInstanceMap != null)
+            {
+                for ( Entry<Contextual<?>, Object> componentEntry : componentInstanceMap.entrySet())
+                {
+                    // there is no nice way to explain the Java Compiler that we are handling the same type T,
+                    // therefore we need completely drop the type information :(
+                    Contextual contextual = componentEntry.getKey();
+                    Object instance = componentEntry.getValue();
+                    CreationalContext creational = creationalContextMap.get(contextual);
+
+                    contextual.destroy(instance, creational);
+                }
+            }
+        }
+    }
+
+
+    protected UIViewRoot getViewRoot()
+    {
+        FacesContext context = FacesContext.getCurrentInstance();
+
+        if(context != null)
+        {
+            return context.getViewRoot();
+        }
+
+        return null;
+    }
+
+    protected Map<String, Object> getViewMap()
+    {
+        UIViewRoot viewRoot = getViewRoot();
+
+        if (viewRoot != null)
+        {
+            return viewRoot.getViewMap(true);
+        }
+
+        return null;
+    }
+}
\ No newline at end of file

Added: myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/javaee/jsf/impl/scope/view/ViewScopedExtension.java
URL: http://svn.apache.org/viewvc/myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/javaee/jsf/impl/scope/view/ViewScopedExtension.java?rev=958778&view=auto
==============================================================================
--- myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/javaee/jsf/impl/scope/view/ViewScopedExtension.java (added)
+++ myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main/java/org/apache/myfaces/extensions/cdi/javaee/jsf/impl/scope/view/ViewScopedExtension.java Mon Jun 28 23:07:23 2010
@@ -0,0 +1,91 @@
+/*
+ * 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.extensions.cdi.javaee.jsf.impl.scope.view;
+
+
+import org.apache.myfaces.extensions.cdi.core.api.projectstage.ProjectStage;
+import org.apache.myfaces.extensions.cdi.core.impl.projectstage.ProjectStageProducer;
+
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.spi.AfterBeanDiscovery;
+import javax.enterprise.inject.spi.BeforeBeanDiscovery;
+import javax.enterprise.inject.spi.Extension;
+import javax.faces.bean.ViewScoped;
+
+
+/**
+ * <p>This CDI extension enables support for the new JSF-2 &#064;ViewScoped annotation.</p>
+ *
+ * <p>2 steps are necessary:</p>
+ * <ol>
+ * <li>We have to manually add the &#064;ViewScoped annotation
+ * as a passivating {@link javax.enterprise.context.NormalScope},
+ * since this annotation is neither a JSR-330 annotation
+ * nor a JSR-299 annotation and therefor doesn't get picked up automatically. This has to be
+ * before the bean scanning starts.</li>
+ * <li>After the bean scanning succeeded, we register the
+ *  {@link javax.enterprise.context.spi.Context} for the
+ * ViewScoped. The {@link ViewScopedContext} is responsible for actually storing all our
+ * &#064;ViewScoped contextual instances in the JSF ViewMap.</li>
+ * </ol>
+ *
+ * <p>The extension automatically detects if we are in the
+ * {@link ProjectStage#UnitTest} and uses
+ * </p>
+ */
+public class ViewScopedExtension implements Extension
+{
+
+    public void addViewScoped(@Observes BeforeBeanDiscovery beforeBeanDiscovery)
+    {
+        beforeBeanDiscovery.addScope(ViewScoped.class, true, true);
+    }
+
+    public void registerViewContext(@Observes AfterBeanDiscovery afterBeanDiscovery)
+    {
+        // we need to do this manually because there is no dependency injection in place
+        // at this time
+        ProjectStageProducer psp = null;
+        try
+        {
+            psp = ProjectStageProducer.getInstance();
+        }
+        catch (Exception e)
+        {
+            if (!(e instanceof RuntimeException))
+            {
+                throw new RuntimeException(e);
+            }
+        }
+        psp.determineProjectStage();
+        ProjectStage projectStage = psp.getProjectStage();
+
+        if (projectStage == ProjectStage.UnitTest)
+        {
+            // for unit tests, we use the mock context
+            afterBeanDiscovery.addContext(new MockViewScopedContext());
+        }
+        else
+        {
+            // otherwise we use the real JSF ViewMap context
+            afterBeanDiscovery.addContext(new ViewScopedContext());
+        }
+    }
+
+}
\ No newline at end of file

Modified: myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
URL: http://svn.apache.org/viewvc/myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension?rev=958778&r1=958777&r2=958778&view=diff
==============================================================================
--- myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension (original)
+++ myfaces/extensions/cdi/trunk/jee-modules/jsf20-module/impl/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension Mon Jun 28 23:07:23 2010
@@ -15,4 +15,7 @@
 # KIND, either express or implied. See the License for the
 # specific language governing permissions and limitations
 # under the License.
-#####################################################################################
\ No newline at end of file
+#####################################################################################
+
+# myfaces-codi support for @ViewScoped
+#org.apache.myfaces.extensions.cdi.javaee.jsf.impl.scope.view.ViewScopedExtension