Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@
import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.EnableDistributable;
import static com.sun.faces.config.WebConfiguration.WebContextInitParameter.NumberOfClientWindows;
import static com.sun.faces.context.SessionMap.getMutex;
import static com.sun.faces.util.Util.getCdiBeanManager;
import static java.util.logging.Level.FINEST;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
Expand All @@ -34,6 +38,7 @@

import jakarta.enterprise.context.spi.Contextual;
import jakarta.enterprise.context.spi.CreationalContext;
import jakarta.enterprise.inject.spi.BeanManager;
import jakarta.enterprise.inject.spi.PassivationCapable;
import jakarta.faces.context.ExternalContext;
import jakarta.faces.context.FacesContext;
Expand All @@ -49,12 +54,28 @@ public class ClientWindowScopeContextManager {

private static final String CLIENT_WINDOW_CONTEXTS = "com.sun.faces.cdi.clientwindow.clientWindowContexts";

private final BeanManager beanManager;
private final boolean distributable;
private final int numberOfClientWindows;

public ClientWindowScopeContextManager() {
FacesContext facesContext = FacesContext.getCurrentInstance();
distributable = WebConfiguration.getInstance(facesContext.getExternalContext())
.isOptionEnabled(EnableDistributable);
beanManager = getCdiBeanManager(facesContext);
WebConfiguration webConfiguration = WebConfiguration.getInstance(facesContext.getExternalContext());
distributable = webConfiguration.isOptionEnabled(EnableDistributable);
numberOfClientWindows = parseNumberOfClientWindows(webConfiguration);
}

private static int parseNumberOfClientWindows(WebConfiguration webConfiguration) {
try {
return Integer.parseInt(webConfiguration.getOptionValue(NumberOfClientWindows));
} catch (NumberFormatException nfe) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING, "Unable to set number of client windows. Defaulting to {0}", NumberOfClientWindows.getDefaultValue());
}

return Integer.parseInt(NumberOfClientWindows.getDefaultValue());
}
}

/**
Expand All @@ -78,7 +99,7 @@ public <T> T createBean(FacesContext facesContext, Contextual<T> contextual, Cre
if (contextualInstance != null) {
String passivationCapableId = ((PassivationCapable) contextual).getId();

getContextMap(facesContext).put(passivationCapableId, new ClientWindowScopeContextObject(passivationCapableId, contextualInstance));
getContextMap(facesContext).put(passivationCapableId, new ClientWindowScopeContextObject(passivationCapableId, contextualInstance, creational));
}

return contextualInstance;
Expand Down Expand Up @@ -144,28 +165,17 @@ private Map<String, ClientWindowScopeContextObject> getContextMap(FacesContext f
String clientWindowId = getCurrentClientWindowId(facesContext);

if (clientWindowScopeContexts == null && create) {
Integer numberOfClientWindows = null;

try {
numberOfClientWindows = Integer.parseInt(WebConfiguration.getInstance(externalContext).getOptionValue(NumberOfClientWindows));
} catch (NumberFormatException nfe) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING, "Unable to set number of client windows. Defaulting to {0}", NumberOfClientWindows.getDefaultValue());
}
}

if (numberOfClientWindows == null) {
numberOfClientWindows = Integer.valueOf(NumberOfClientWindows.getDefaultValue());
}

synchronized (getMutex(session)) {
sessionMap.put(CLIENT_WINDOW_CONTEXTS, Collections.synchronizedMap(new LRUMap<String, Object>(numberOfClientWindows)));
}
}

if (clientWindowScopeContexts != null && clientWindowId != null && create) {
Map<String, ClientWindowScopeContextObject> evictedContextMap = null;

synchronized (clientWindowScopeContexts) {
if (!clientWindowScopeContexts.containsKey(clientWindowId)) {
evictedContextMap = evictEldestContextMap(clientWindowScopeContexts);
clientWindowScopeContexts.put(clientWindowId,
new ConcurrentHashMap<String, ClientWindowScopeContextObject>());
if (distributable) {
Expand All @@ -176,6 +186,10 @@ private Map<String, ClientWindowScopeContextObject> getContextMap(FacesContext f
}
}
}

if (evictedContextMap != null) {
destroyBeans(evictedContextMap);
}
}

if (clientWindowScopeContexts != null && clientWindowId != null) {
Expand All @@ -187,6 +201,41 @@ private Map<String, ClientWindowScopeContextObject> getContextMap(FacesContext f
return result;
}

/**
* Remove the context map of the least recently used client window when there is no room for another one.
* The caller must hold the lock on the given map.
*
* @param clientWindowScopeContexts the access ordered context maps of all client windows of the session.
* @return the removed context map, or null when there is still room.
*/
private Map<String, ClientWindowScopeContextObject> evictEldestContextMap(Map<Object, Map<String, ClientWindowScopeContextObject>> clientWindowScopeContexts) {
if (clientWindowScopeContexts.size() < numberOfClientWindows) {
return null;
}

Iterator<Map<String, ClientWindowScopeContextObject>> contextMaps = clientWindowScopeContexts.values().iterator();
Map<String, ClientWindowScopeContextObject> eldestContextMap = contextMaps.next();
contextMaps.remove();
return eldestContextMap;
}

/**
* Destroy the client window scoped beans of the given context map. A bean which cannot be destroyed is logged and skipped.
*/
@SuppressWarnings("unchecked")
private void destroyBeans(Map<String, ClientWindowScopeContextObject> contextMap) {
for (ClientWindowScopeContextObject contextObject : contextMap.values()) {
try {
Contextual contextual = beanManager.getPassivationCapableBean(contextObject.getPassivationCapableId());
CreationalContext creationalContext = contextObject.getCreationalContext();
// Absent in a session which was serialized by a version which did not store it yet.
contextual.destroy(contextObject.getContextualInstance(), creationalContext != null ? creationalContext : beanManager.createCreationalContext(contextual));
} catch (RuntimeException e) {
LOGGER.log(Level.WARNING, "Cannot destroy the @ClientWindowScoped bean: " + contextObject.getPassivationCapableId(), e);
}
}
}

/**
* Called when a session destroyed.
*
Expand All @@ -203,7 +252,14 @@ public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
Map<Object, Map<String, ClientWindowScopeContextObject>> clientWindowScopeContexts = (Map<Object, Map<String, ClientWindowScopeContextObject>>)
session.getAttribute(CLIENT_WINDOW_CONTEXTS);
if (clientWindowScopeContexts != null) {
clientWindowScopeContexts.clear();
List<Map<String, ClientWindowScopeContextObject>> contextMaps;

synchronized (clientWindowScopeContexts) {
contextMaps = new ArrayList<>(clientWindowScopeContexts.values());
clientWindowScopeContexts.clear();
}

contextMaps.forEach(this::destroyBeans);
session.removeAttribute(CLIENT_WINDOW_CONTEXTS);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import java.io.Serializable;

import jakarta.enterprise.context.spi.CreationalContext;

/**
* An object used by ClientWindowScopeContext to keep track of contextual and creational context.
*/
Expand All @@ -31,9 +33,15 @@ class ClientWindowScopeContextObject<T> implements Serializable {
*/
private T contextualInstance;

public ClientWindowScopeContextObject(String passivationCapableId, T contextualInstance) {
/**
* The creational context which created the contextual instance. Only this one holds its dependent objects.
*/
private CreationalContext<T> creationalContext;

public ClientWindowScopeContextObject(String passivationCapableId, T contextualInstance, CreationalContext<T> creationalContext) {
this.passivationCapableId = passivationCapableId;
this.contextualInstance = contextualInstance;
this.creationalContext = creationalContext;
}

public String getPassivationCapableId() {
Expand All @@ -51,4 +59,8 @@ public T getContextualInstance() {
public void setContextualInstance(T contextualInstance) {
this.contextualInstance = contextualInstance;
}

public CreationalContext<T> getCreationalContext() {
return creationalContext;
}
}
41 changes: 41 additions & 0 deletions test/issue6152/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--

Copyright (c) Contributors to Eclipse Foundation.

This program and the accompanying materials are made available under the
terms of the Eclipse Public License v. 2.0, which is available at
http://www.eclipse.org/legal/epl-2.0.

This Source Code may also be made available under the following Secondary
Licenses when the conditions for such availability set forth in the
Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
version 2 with the GNU Classpath Exception, which is available at
https://www.gnu.org/software/classpath/license.html.

SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0

-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.eclipse.mojarra.test</groupId>
<artifactId>pom</artifactId>
<version>4.0.30-SNAPSHOT</version>
</parent>

<artifactId>issue6152</artifactId>
<packaging>war</packaging>

<name>Mojarra ${project.version} - INTEGRATION TESTS - ${project.artifactId}</name>

<dependencies>
<dependency>
<groupId>org.eclipse.mojarra.test</groupId>
<artifactId>base</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright (c) Contributors to the Eclipse Foundation.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GPL-2.0 with Classpath-exception-2.0 which
* is available at https://openjdk.java.net/legal/gplv2+ce.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 or Apache-2.0
*/
package org.eclipse.mojarra.test.issue6152;

import java.io.Serializable;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.faces.lifecycle.ClientWindowScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;

@Named
@ClientWindowScoped
public class Issue6152Bean implements Serializable {

private static final long serialVersionUID = 1L;

@Inject
private Issue6152Counter counter;

@Inject
private Issue6152Dependency dependency;

@PostConstruct
public void init() {
counter.created();
}

@PreDestroy
public void destroy() {
counter.destroyed();
}

public String getValue() {
return "value";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (c) Contributors to the Eclipse Foundation.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GPL-2.0 with Classpath-exception-2.0 which
* is available at https://openjdk.java.net/legal/gplv2+ce.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 or Apache-2.0
*/
package org.eclipse.mojarra.test.issue6152;

import java.util.concurrent.atomic.AtomicInteger;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Named;

@Named
@ApplicationScoped
public class Issue6152Counter {

private final AtomicInteger created = new AtomicInteger();
private final AtomicInteger destroyed = new AtomicInteger();
private final AtomicInteger dependencyDestroyed = new AtomicInteger();

public void created() {
created.incrementAndGet();
}

public void destroyed() {
destroyed.incrementAndGet();
}

public void dependencyDestroyed() {
dependencyDestroyed.incrementAndGet();
}

public int getCreated() {
return created.get();
}

public int getDestroyed() {
return destroyed.get();
}

public int getDependencyDestroyed() {
return dependencyDestroyed.get();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (c) Contributors to the Eclipse Foundation.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GPL-2.0 with Classpath-exception-2.0 which
* is available at https://openjdk.java.net/legal/gplv2+ce.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 or Apache-2.0
*/
package org.eclipse.mojarra.test.issue6152;

import java.io.Serializable;

import jakarta.annotation.PreDestroy;
import jakarta.enterprise.context.Dependent;
import jakarta.inject.Inject;

@Dependent
public class Issue6152Dependency implements Serializable {

private static final long serialVersionUID = 1L;

@Inject
private Issue6152Counter counter;

@PreDestroy
public void destroy() {
counter.dependencyDestroyed();
}
}
Loading
Loading