| /* |
| * 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.deltaspike.core.util; |
| |
| import org.apache.deltaspike.core.api.config.ConfigResolver; |
| import org.apache.deltaspike.core.api.config.base.CoreBaseConfig; |
| import org.apache.deltaspike.core.api.projectstage.ProjectStage; |
| import org.apache.deltaspike.core.spi.activation.ClassDeactivator; |
| import org.apache.deltaspike.core.spi.activation.Deactivatable; |
| |
| import javax.enterprise.inject.Typed; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| /** |
| * Helper methods for {@link ClassDeactivator} |
| */ |
| @Typed() |
| public abstract class ClassDeactivationUtils |
| { |
| private static final Logger LOG = Logger.getLogger(ClassDeactivationUtils.class.getName()); |
| |
| /** |
| * This Map holds the ClassLoader as first level to make it possible to have different configurations per |
| * WebApplication in an EAR or other Multi-ClassLoader scenario. |
| * |
| * The Map then contains a List of {@link ClassDeactivator}s in order of their configured ordinal. |
| */ |
| private static Map<ClassLoader, List<ClassDeactivator>> classDeactivatorMap |
| = new ConcurrentHashMap<ClassLoader, List<ClassDeactivator>>(); |
| |
| /** |
| * Cache for the result. It won't contain many classes but it might be accessed frequently. |
| * Valid entries are only true or false. If an entry isn't available or null, it gets calculated. |
| */ |
| private static Map<Class<? extends Deactivatable>, Boolean> activationStatusCache |
| = new ConcurrentHashMap<Class<? extends Deactivatable>, Boolean>(); |
| |
| private static ProjectStage previouslyDetectedProjectStage; |
| |
| private ClassDeactivationUtils() |
| { |
| // prevent instantiation |
| } |
| |
| /** |
| * Flush the caches to prevent ClassLoader leaks. |
| * This is called internally by DeltaSpike. |
| * Users do not have to explicitly call this method. |
| * Does not have side effects as the cache is idempotent anyway. |
| */ |
| public static void clearCache() |
| { |
| classDeactivatorMap.clear(); |
| } |
| |
| /** |
| * Evaluates if the given {@link Deactivatable} is active. |
| * |
| * @param targetClass {@link Deactivatable} under test. |
| * @return <code>true</code> if it is active, <code>false</code> otherwise |
| */ |
| public static boolean isActivated(Class<? extends Deactivatable> targetClass) |
| { |
| performProjectStageDependentCleanup(); |
| |
| //just to support parallel access to #isActivated (+ reset) in project-stage unit-test and development |
| Map<Class<? extends Deactivatable>, Boolean> activeCache = activationStatusCache; |
| Boolean activatedClassCacheEntry = activeCache.get(targetClass); |
| |
| if (activatedClassCacheEntry == null) |
| { |
| initDeactivatableCacheFor(targetClass, activeCache); |
| activatedClassCacheEntry = activeCache.get(targetClass); |
| } |
| return activatedClassCacheEntry; |
| } |
| |
| private static void performProjectStageDependentCleanup() |
| { |
| ProjectStage currentProjectStage = ProjectStageProducer.getInstance().getProjectStage(); |
| |
| if (previouslyDetectedProjectStage != currentProjectStage) |
| { |
| previouslyDetectedProjectStage = currentProjectStage; |
| //don't use #clear to support parallel access to #isActivated (+ reset) without synchronization |
| activationStatusCache = new ConcurrentHashMap<Class<? extends Deactivatable>, Boolean>(); |
| |
| //#clear is ok here due to the handling in the synchronized method #initDeactivatableCacheFor |
| classDeactivatorMap.clear(); |
| } |
| else if (currentProjectStage == ProjectStage.UnitTest || currentProjectStage == ProjectStage.Development) |
| { |
| activationStatusCache = new ConcurrentHashMap<Class<? extends Deactivatable>, Boolean>(); |
| } |
| } |
| |
| private static synchronized void initDeactivatableCacheFor(Class<? extends Deactivatable> targetClass, |
| Map<Class<? extends Deactivatable>, Boolean> activeCache) |
| { |
| Boolean activatedClassCacheEntry = activeCache.get(targetClass); |
| |
| if (activatedClassCacheEntry != null) //double-check |
| { |
| return; |
| } |
| |
| List<ClassDeactivator> classDeactivators = getClassDeactivators(); |
| |
| Boolean isActivated = Boolean.TRUE; |
| Class<? extends ClassDeactivator> deactivatedBy = null; |
| |
| LOG.fine("start evaluation if " + targetClass.getName() + " is de-/activated"); |
| |
| // we get the classActivators ordered by it's ordinal |
| // thus the last one which returns != null 'wins' ;) |
| for (ClassDeactivator classDeactivator : classDeactivators) |
| { |
| Boolean isLocallyActivated = classDeactivator.isActivated(targetClass); |
| |
| if (isLocallyActivated != null) |
| { |
| isActivated = isLocallyActivated; |
| |
| /* |
| * Check and log the details across class-deactivators |
| */ |
| if (!isActivated) |
| { |
| deactivatedBy = classDeactivator.getClass(); |
| LOG.fine("Deactivating class " + targetClass); |
| } |
| else if (deactivatedBy != null) |
| { |
| LOG.fine("Reactivation of: " + targetClass.getName() + " by " + |
| classDeactivator.getClass().getName() + |
| " - original deactivated by: " + deactivatedBy.getName() + ".\n" + |
| "If that isn't the intended behaviour, you have to use a higher ordinal for " + |
| deactivatedBy.getName()); |
| } |
| } |
| } |
| |
| cacheResult(targetClass, isActivated, activeCache); |
| } |
| |
| private static void cacheResult(Class<? extends Deactivatable> targetClass, Boolean activated, |
| Map<Class<? extends Deactivatable>, Boolean> activeCache) |
| { |
| activeCache.put(targetClass, activated); |
| if (LOG.isLoggable(Level.FINE)) |
| { |
| LOG.fine("class: " + targetClass.getName() + " activated=" + activated); |
| } |
| } |
| |
| /** |
| * @return the List of configured @{link ClassDeactivator}s for the current context ClassLoader. |
| */ |
| private static List<ClassDeactivator> getClassDeactivators() |
| { |
| ClassLoader classLoader = ClassUtils.getClassLoader(null); |
| List<ClassDeactivator> classDeactivators = classDeactivatorMap.get(classLoader); |
| |
| if (classDeactivators == null) |
| { |
| return initConfiguredClassDeactivators(classLoader); |
| } |
| |
| return classDeactivators; |
| } |
| |
| //synchronized isn't needed - #initDeactivatableCacheFor is already synchronized |
| private static List<ClassDeactivator> initConfiguredClassDeactivators(ClassLoader classLoader) |
| { |
| if (!ServiceUtils.loadServiceImplementations(ClassDeactivator.class).isEmpty()) |
| { |
| CoreBaseConfig.Validation.ViolationMode violationMode = CoreBaseConfig.Validation.VIOLATION_MODE; |
| |
| String message = "It isn't supported to configure " + ClassDeactivator.class.getName() + |
| " via the std. service-loader config. " + |
| "Please remove all META-INF/services/" + ClassDeactivator.class.getName() + " files. " + |
| "Please configure it via the DeltaSpike-Config (e.g. META-INF/apache-deltaspike.properties)."; |
| |
| if (violationMode == CoreBaseConfig.Validation.ViolationMode.FAIL) |
| { |
| throw new IllegalStateException(message); |
| } |
| else if (violationMode == CoreBaseConfig.Validation.ViolationMode.WARN) |
| { |
| LOG.warning(message); |
| } |
| } |
| |
| List<String> classDeactivatorClassNames = ConfigResolver.getAllPropertyValues(ClassDeactivator.class.getName()); |
| |
| List<ClassDeactivator> classDeactivators = new ArrayList<ClassDeactivator>(); |
| |
| for (String classDeactivatorClassName : classDeactivatorClassNames) |
| { |
| LOG.fine("processing ClassDeactivator: " + classDeactivatorClassName); |
| |
| try |
| { |
| ClassDeactivator currentClassDeactivator = |
| (ClassDeactivator) ClassUtils.instantiateClassForName(classDeactivatorClassName); |
| classDeactivators.add(currentClassDeactivator); |
| } |
| catch (Exception e) |
| { |
| LOG.warning(classDeactivatorClassName + " can't be instantiated"); |
| throw new IllegalStateException(e); |
| } |
| } |
| |
| classDeactivatorMap.put(classLoader, classDeactivators); |
| return classDeactivators; |
| } |
| } |