
io.micronaut.context.scope.AbstractConcurrentCustomScope Maven / Gradle / Ivy
/*
* Copyright 2017-2021 original authors
*
* Licensed 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
*
* https://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 io.micronaut.context.scope;
import io.micronaut.context.BeanRegistration;
import io.micronaut.context.LifeCycle;
import io.micronaut.context.exceptions.BeanDestructionException;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.BeanIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.annotation.Annotation;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Abstract implementation of the custom scope interface that simplifies defining new scopes using the Map interface.
*
* Note this implementation uses a single{@link ReentrantReadWriteLock} to lock the entire scope hence it is designed for scopes that will hold a small amount of beans. For implementations that hold many beans it is recommended to use a lock per {@link BeanIdentifier}.
*
* @param The annotation type
* @author graemerocher
* @since 3.0.0
*/
public abstract class AbstractConcurrentCustomScope implements CustomScope, LifeCycle>, AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(AbstractConcurrentCustomScope.class);
private final Class annotationType;
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();
/**
* A custom scope annotation.
*
* @param annotationType The annotation type
*/
protected AbstractConcurrentCustomScope(Class annotationType) {
this.annotationType = Objects.requireNonNull(annotationType, "Annotation type cannot be null");
}
/**
* @param forCreation Whether it is for creation
* @return Obtains the scope map, never null
* @throws java.lang.IllegalStateException if the scope map cannot be obtained in the current context
*/
@NonNull
protected abstract Map> getScopeMap(boolean forCreation);
@Override
public final Class annotationType() {
return annotationType;
}
/**
* Implement the close logic for the scope.
*/
@Override
public abstract void close();
@NonNull
@Override
public final AbstractConcurrentCustomScope stop() {
w.lock();
try {
try {
final Map> scopeMap = getScopeMap(false);
destroyScope(scopeMap);
} catch (IllegalStateException e) {
// scope map not available in current context
}
close();
return this;
} finally {
w.unlock();
}
}
/**
* Destroys the scope.
*
* @param scopeMap The scope map
*/
protected void destroyScope(@Nullable Map> scopeMap) {
w.lock();
try {
if (CollectionUtils.isNotEmpty(scopeMap)) {
for (CreatedBean> createdBean : scopeMap.values()) {
try {
createdBean.close();
} catch (BeanDestructionException e) {
handleDestructionException(e);
}
}
scopeMap.clear();
}
} finally {
w.unlock();
}
}
@SuppressWarnings("unchecked")
@Override
public final T getOrCreate(BeanCreationContext creationContext) {
r.lock();
try {
final Map> scopeMap = getScopeMap(true);
final BeanIdentifier id = creationContext.id();
CreatedBean> createdBean = scopeMap.get(id);
if (createdBean != null) {
return (T) createdBean.bean();
} else {
r.unlock();
w.lock();
try {
// re-check
createdBean = scopeMap.get(id);
if (createdBean != null) {
r.lock();
return (T) createdBean.bean();
} else {
try {
createdBean = doCreate(creationContext);
scopeMap.put(id, createdBean);
} finally {
r.lock();
}
return (T) createdBean.bean();
}
} finally {
w.unlock();
}
}
} finally {
r.unlock();
}
}
/**
* Perform creation.
* @param creationContext The creation context
* @param The generic type
* @return Created bean
*/
@NonNull
protected CreatedBean doCreate(@NonNull BeanCreationContext creationContext) {
return creationContext.create();
}
@Override
public final Optional remove(BeanIdentifier identifier) {
if (identifier == null) {
return Optional.empty();
}
w.lock();
try {
final Map> scopeMap;
try {
scopeMap = getScopeMap(false);
} catch (IllegalStateException e) {
return Optional.empty();
}
if (CollectionUtils.isNotEmpty(scopeMap)) {
final CreatedBean> createdBean = scopeMap.get(identifier);
if (createdBean != null) {
try {
createdBean.close();
} catch (BeanDestructionException e) {
handleDestructionException(e);
}
//noinspection ConstantConditions
return (Optional) Optional.ofNullable(createdBean.bean());
} else {
return Optional.empty();
}
} else {
return Optional.empty();
}
} finally {
w.unlock();
}
}
/**
* Method that can be overridden to customize what happens on a shutdown error.
* @param e The exception
*/
protected void handleDestructionException(BeanDestructionException e) {
LOG.error("Error occurred destroying bean of scope @{}: {}", annotationType.getSimpleName(), e.getMessage(), e);
}
@SuppressWarnings("unchecked")
@Override
public final Optional> findBeanRegistration(T bean) {
r.lock();
try {
final Map> scopeMap;
try {
scopeMap = getScopeMap(false);
} catch (Exception e) {
return Optional.empty();
}
for (CreatedBean> createdBean : scopeMap.values()) {
if (createdBean.bean() == bean) {
if (createdBean instanceof BeanRegistration) {
return Optional.of((BeanRegistration) createdBean);
}
return Optional.of(
new BeanRegistration<>(
createdBean.id(),
(BeanDefinition) createdBean.definition(),
bean
)
);
}
}
return Optional.empty();
} finally {
r.unlock();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy