| /* |
| * 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.impl.config; |
| |
| import javax.enterprise.context.ApplicationScoped; |
| import javax.enterprise.context.spi.CreationalContext; |
| import javax.enterprise.event.Observes; |
| import javax.enterprise.inject.Produces; |
| import javax.enterprise.inject.Typed; |
| import javax.enterprise.inject.spi.AfterBeanDiscovery; |
| import javax.enterprise.inject.spi.AfterDeploymentValidation; |
| import javax.enterprise.inject.spi.Bean; |
| import javax.enterprise.inject.spi.BeanManager; |
| import javax.enterprise.inject.spi.BeforeBeanDiscovery; |
| import javax.enterprise.inject.spi.BeforeShutdown; |
| import javax.enterprise.inject.spi.Extension; |
| import javax.enterprise.inject.spi.InjectionPoint; |
| import javax.enterprise.inject.spi.ProcessAnnotatedType; |
| import javax.enterprise.inject.spi.ProcessBean; |
| import javax.enterprise.inject.spi.ProcessProducerMethod; |
| import javax.management.InstanceAlreadyExistsException; |
| import javax.management.InstanceNotFoundException; |
| import javax.management.MBeanServer; |
| import javax.management.ObjectName; |
| |
| import java.lang.annotation.Annotation; |
| import java.lang.management.ManagementFactory; |
| import java.lang.reflect.Type; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| import org.apache.deltaspike.core.api.config.ConfigProperty; |
| import org.apache.deltaspike.core.api.config.ConfigResolver; |
| import org.apache.deltaspike.core.api.config.Configuration; |
| import org.apache.deltaspike.core.api.config.Filter; |
| import org.apache.deltaspike.core.api.config.PropertyFileConfig; |
| import org.apache.deltaspike.core.api.config.Source; |
| import org.apache.deltaspike.core.api.exclude.Exclude; |
| import org.apache.deltaspike.core.api.literal.AnyLiteral; |
| import org.apache.deltaspike.core.api.literal.DefaultLiteral; |
| import org.apache.deltaspike.core.api.provider.BeanProvider; |
| import org.apache.deltaspike.core.spi.activation.Deactivatable; |
| import org.apache.deltaspike.core.spi.config.BaseConfigPropertyProducer; |
| import org.apache.deltaspike.core.spi.config.ConfigFilter; |
| import org.apache.deltaspike.core.spi.config.ConfigSource; |
| import org.apache.deltaspike.core.spi.config.ConfigValidator; |
| import org.apache.deltaspike.core.util.ClassDeactivationUtils; |
| import org.apache.deltaspike.core.util.ClassUtils; |
| import org.apache.deltaspike.core.util.ServiceUtils; |
| import org.apache.deltaspike.core.util.bean.BeanBuilder; |
| |
| /** |
| * This extension handles {@link org.apache.deltaspike.core.api.config.PropertyFileConfig}s |
| * provided by users. |
| */ |
| public class ConfigurationExtension implements Extension, Deactivatable |
| { |
| private static final Logger LOG = Logger.getLogger(ConfigurationExtension.class.getName()); |
| |
| private static final String CANNOT_CREATE_CONFIG_SOURCE_FOR_CUSTOM_PROPERTY_FILE_CONFIG = |
| "Cannot create ConfigSource for custom property-file config "; |
| |
| /** |
| * This is a trick for EAR scenarios in some containers. |
| * They e.g. boot up the shared EAR lib with the ear ClassLoader. |
| * Thus any {@link org.apache.deltaspike.core.api.config.PropertyFileConfig} configuration will just get |
| * activated for this very single EAR ClassLoader but <em>not</em> for all the webapps. |
| * But if I have a property file in a jar in the shared EAR lib then I most likely also like to get it |
| * if I call this from my webapp (TCCL). |
| * So we also automatically register all the PropertyFileConfigs we found in the 'parent BeanManager' |
| * as well. |
| */ |
| private static Map<ClassLoader, List<Class<? extends PropertyFileConfig>>> detectedParentPropertyFileConfigs |
| = new ConcurrentHashMap<ClassLoader, List<Class<? extends PropertyFileConfig>>>(); |
| |
| private boolean isActivated = true; |
| |
| private List<Class<? extends PropertyFileConfig>> propertyFileConfigClasses |
| = new ArrayList<Class<? extends PropertyFileConfig>>(); |
| |
| private final Set<Type> dynamicConfigTypes = new HashSet<Type>(); |
| private Bean<DynamicBeanProducer> dynamicProducer; |
| |
| private final List<Bean<? extends ConfigSource>> cdiSources = new ArrayList<Bean<? extends ConfigSource>>(); |
| private final List<Bean<? extends ConfigFilter>> cdiFilters = new ArrayList<Bean<? extends ConfigFilter>>(); |
| private final List<Class<?>> dynamicConfigurationBeanClasses = new ArrayList<Class<?>>(); |
| |
| @SuppressWarnings("UnusedDeclaration") |
| protected void init(@Observes BeforeBeanDiscovery beforeBeanDiscovery) |
| { |
| isActivated = ClassDeactivationUtils.isActivated(getClass()); |
| } |
| |
| public static void registerConfigMBean() |
| { |
| String appName = ConfigResolver.getPropertyValue(ConfigResolver.DELTASPIKE_APP_NAME_CONFIG); |
| if (appName != null && appName.length() > 0) |
| { |
| try |
| { |
| MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); |
| |
| ClassLoader tccl = ClassUtils.getClassLoader(ConfigurationExtension.class); |
| DeltaSpikeConfigInfo cfgMBean = new DeltaSpikeConfigInfo(tccl); |
| |
| ObjectName name = new ObjectName("deltaspike.config." + appName + ":type=DeltaSpikeConfig"); |
| mBeanServer.registerMBean(cfgMBean, name); |
| } |
| catch (InstanceAlreadyExistsException iae) |
| { |
| // all fine, the CfgBean got already registered. |
| // Most probably by the ServletConfigListener |
| } |
| catch (Exception e) |
| { |
| throw new RuntimeException(e); |
| } |
| } |
| } |
| |
| public static void unRegisterConfigMBean(String appName) |
| { |
| if (appName != null && appName.length() > 0) |
| { |
| try |
| { |
| MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); |
| |
| ObjectName name = new ObjectName("deltaspike.config." + appName + ":type=DeltaSpikeConfig"); |
| |
| mBeanServer.unregisterMBean(name); |
| } |
| catch (InstanceNotFoundException infe) |
| { |
| // all ok, nothing to de-register it seems |
| } |
| catch (Exception e) |
| { |
| throw new RuntimeException(e); |
| } |
| } |
| } |
| |
| @SuppressWarnings("UnusedDeclaration") |
| public void collectUserConfigSources(@Observes ProcessAnnotatedType<? extends PropertyFileConfig> pat) |
| { |
| if (!isActivated) |
| { |
| return; |
| } |
| |
| Class<? extends PropertyFileConfig> pcsClass = pat.getAnnotatedType().getJavaClass(); |
| if (pcsClass.isAnnotation() || |
| pcsClass.isInterface() || |
| pcsClass.isSynthetic() || |
| pcsClass.isArray() || |
| pcsClass.isEnum() ) |
| { |
| // we only like to add real classes |
| return; |
| } |
| |
| if (pat.getAnnotatedType().isAnnotationPresent(Exclude.class)) |
| { |
| // We only pick up PropertyFileConfigs if they are not excluded |
| // This can be the case for PropertyFileConfigs which are registered via java.util.ServiceLoader |
| return; |
| } |
| |
| propertyFileConfigClasses.add(pcsClass); |
| } |
| |
| public void findDynamicConfigurationBeans(@Observes ProcessAnnotatedType<?> pat) |
| { |
| if (!pat.getAnnotatedType().isAnnotationPresent(Configuration.class)) |
| { |
| return; |
| } |
| final Class<?> javaClass = pat.getAnnotatedType().getJavaClass(); |
| if (!javaClass.isInterface()) |
| { |
| return; |
| } |
| dynamicConfigurationBeanClasses.add(javaClass); |
| } |
| |
| public void findSources(@Observes ProcessBean<? extends ConfigSource> source) |
| { |
| if (!source.getAnnotated().isAnnotationPresent(Source.class)) |
| { |
| return; |
| } |
| cdiSources.add(source.getBean()); |
| } |
| |
| public void findFilters(@Observes ProcessBean<? extends ConfigFilter> filter) |
| { |
| if (!filter.getAnnotated().isAnnotationPresent(Filter.class)) |
| { |
| return; |
| } |
| cdiFilters.add(filter.getBean()); |
| } |
| |
| public void findDynamicProducer(@Observes ProcessProducerMethod<?, DynamicBeanProducer> processBean) |
| { |
| dynamicProducer = processBean.getBean(); |
| } |
| |
| public void collectDynamicTypes(@Observes ProcessBean<?> processBean) |
| { |
| for (final InjectionPoint ip : processBean.getBean().getInjectionPoints()) |
| { |
| final ConfigProperty annotation = ip.getAnnotated().getAnnotation(ConfigProperty.class); |
| if (annotation == null || annotation.converter() == ConfigResolver.Converter.class) |
| { |
| continue; |
| } |
| |
| dynamicConfigTypes.add(ip.getType()); |
| } |
| } |
| |
| public void addDynamicBeans(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager bm) |
| { |
| if (dynamicProducer != null && !dynamicConfigTypes.isEmpty()) |
| { |
| afterBeanDiscovery.addBean(new DynamicBean(dynamicProducer, dynamicConfigTypes)); |
| } |
| for (final Class<?> proxyType : dynamicConfigurationBeanClasses) |
| { |
| afterBeanDiscovery.addBean(new BeanBuilder(null) |
| .types(proxyType, Object.class) |
| .qualifiers(new DefaultLiteral(), new AnyLiteral()) |
| .beanLifecycle(new ProxyConfigurationLifecycle(proxyType)) |
| .scope(ApplicationScoped.class) |
| .passivationCapable(true) |
| .id("DeltaSpikeConfiguration#" + proxyType.getName()) |
| .beanClass(proxyType) |
| .create()); |
| } |
| } |
| |
| @SuppressWarnings("UnusedDeclaration") |
| public void registerUserConfigSources(@Observes AfterBeanDiscovery abd) |
| { |
| if (!isActivated) |
| { |
| return; |
| } |
| |
| // create a local copy with all the collected PropertyFileConfig |
| Set<Class<? extends PropertyFileConfig>> allPropertyFileConfigClasses |
| = new HashSet<Class<? extends PropertyFileConfig>>(this.propertyFileConfigClasses); |
| |
| // now add any PropertyFileConfigs from a 'parent BeanManager' |
| // we start with the current TCCL |
| ClassLoader currentClassLoader = ClassUtils.getClassLoader(null); |
| addParentPropertyFileConfigs(currentClassLoader, allPropertyFileConfigClasses); |
| |
| // now let's add our own PropertyFileConfigs to the detected ones. |
| // because maybe WE are a parent BeanManager ourselves! |
| if (!this.propertyFileConfigClasses.isEmpty()) |
| { |
| detectedParentPropertyFileConfigs.put(currentClassLoader, this.propertyFileConfigClasses); |
| } |
| |
| // collect all the ConfigSources from our PropertyFileConfigs |
| List<ConfigSource> configSources = new ArrayList<ConfigSource>(); |
| for (Class<? extends PropertyFileConfig> propertyFileConfigClass : allPropertyFileConfigClasses) |
| { |
| configSources.addAll(createPropertyConfigSource(propertyFileConfigClass)); |
| } |
| ConfigResolver.addConfigSources(configSources); |
| |
| registerConfigMBean(); |
| |
| logConfiguration(); |
| } |
| |
| public void validateConfiguration(@Observes AfterDeploymentValidation adv) |
| { |
| List<ConfigSource> configSources = new ArrayList<ConfigSource>(cdiSources.size()); |
| for (final Bean bean : cdiSources) |
| { |
| configSources.add(BeanProvider.getContextualReference(ConfigSource.class, bean)); |
| } |
| ConfigResolver.addConfigSources(configSources); |
| |
| for (final Bean bean : cdiFilters) |
| { |
| ConfigResolver.addConfigFilter(BeanProvider.getContextualReference(ConfigFilter.class, bean)); |
| } |
| |
| processConfigurationValidation(adv); |
| } |
| |
| private void logConfiguration() |
| { |
| Boolean logConfig = ConfigResolver.resolve(ConfigResolver.DELTASPIKE_LOG_CONFIG).as(Boolean.class).getValue(); |
| if (logConfig != null && logConfig && LOG.isLoggable(Level.INFO)) |
| { |
| StringBuilder sb = new StringBuilder(1 << 16); |
| |
| // first log out the config sources in descendent ordinal order |
| sb.append("ConfigSources: "); |
| ConfigSource[] configSources = ConfigResolver.getConfigSources(); |
| for (ConfigSource configSource : configSources) |
| { |
| sb.append("\n\t").append(configSource.getOrdinal()).append(" - ").append(configSource.getConfigName()); |
| } |
| |
| // and all the entries in no guaranteed order |
| Map<String, String> allProperties = ConfigResolver.getAllProperties(); |
| sb.append("\n\nConfigured Values:"); |
| for (Map.Entry<String, String> entry : allProperties.entrySet()) |
| { |
| sb.append("\n\t") |
| .append(entry.getKey()) |
| .append(" = ") |
| .append(ConfigResolver.filterConfigValueForLog(entry.getKey(), entry.getValue())); |
| } |
| |
| LOG.info(sb.toString()); |
| } |
| } |
| |
| /** |
| * Add all registered PropertyFileConfigs which got picked up in a parent ClassLoader already |
| */ |
| private void addParentPropertyFileConfigs(ClassLoader currentClassLoader, |
| Set<Class<? extends PropertyFileConfig>> propertyFileConfigClasses) |
| { |
| if (currentClassLoader.getParent() == null) |
| { |
| return; |
| } |
| |
| for (Map.Entry<ClassLoader, List<Class<? extends PropertyFileConfig>>> classLoaderListEntry : |
| detectedParentPropertyFileConfigs.entrySet()) |
| { |
| if (currentClassLoader.getParent().equals(classLoaderListEntry.getKey())) |
| { |
| // if this is the direct parent ClassLoader then lets add those PropertyFileConfigs. |
| propertyFileConfigClasses.addAll(classLoaderListEntry.getValue()); |
| |
| // even check further parents |
| addParentPropertyFileConfigs(classLoaderListEntry.getKey(), propertyFileConfigClasses); |
| |
| // and be done. There can only be a single parent CL... |
| return; |
| } |
| } |
| } |
| |
| /** |
| * This method triggers freeing of the ConfigSources. |
| */ |
| @SuppressWarnings("UnusedDeclaration") |
| public void freeConfigSources(@Observes BeforeShutdown bs) |
| { |
| String appName = ConfigResolver.getPropertyValue(ConfigResolver.DELTASPIKE_APP_NAME_CONFIG); |
| unRegisterConfigMBean(appName); |
| |
| ConfigResolver.freeConfigSources(); |
| detectedParentPropertyFileConfigs.remove(ClassUtils.getClassLoader(null)); |
| |
| // we also free the ClassDeactivationUtils cache |
| ClassDeactivationUtils.clearCache(); |
| } |
| |
| /** |
| * @return create an instance of the given {@link PropertyFileConfig} and return all it's ConfigSources. |
| */ |
| private List<ConfigSource> createPropertyConfigSource(Class<? extends PropertyFileConfig> propertyFileConfigClass) |
| { |
| String fileName = ""; |
| try |
| { |
| PropertyFileConfig propertyFileConfig = propertyFileConfigClass.newInstance(); |
| fileName = propertyFileConfig.getPropertyFileName(); |
| EnvironmentPropertyConfigSourceProvider environmentPropertyConfigSourceProvider |
| = new EnvironmentPropertyConfigSourceProvider(fileName, propertyFileConfig.isOptional()); |
| |
| return environmentPropertyConfigSourceProvider.getConfigSources(); |
| } |
| catch (InstantiationException e) |
| { |
| throw new RuntimeException(CANNOT_CREATE_CONFIG_SOURCE_FOR_CUSTOM_PROPERTY_FILE_CONFIG + |
| propertyFileConfigClass.getName(), e); |
| } |
| catch (IllegalAccessException e) |
| { |
| throw new RuntimeException(CANNOT_CREATE_CONFIG_SOURCE_FOR_CUSTOM_PROPERTY_FILE_CONFIG + |
| propertyFileConfigClass.getName(), e); |
| } |
| catch (IllegalStateException e) |
| { |
| throw new IllegalStateException( |
| propertyFileConfigClass.getName() + " points to an invalid file: '" + fileName + "'", e); |
| } |
| } |
| |
| protected void processConfigurationValidation(AfterDeploymentValidation adv) |
| { |
| for (ConfigValidator configValidator : ServiceUtils.loadServiceImplementations(ConfigValidator.class)) |
| { |
| Set<String> violations = configValidator.processValidation(); |
| |
| if (violations == null) |
| { |
| continue; |
| } |
| |
| for (String violation : violations) |
| { |
| adv.addDeploymentProblem(new IllegalStateException(violation)); |
| } |
| } |
| } |
| |
| @ApplicationScoped |
| @Typed(DynamicBeanProducer.class) // used as an internal bean |
| static class DynamicBeanProducer extends BaseConfigPropertyProducer |
| { |
| @Produces |
| @ConfigProperty(name = "ignored") |
| public Object create(final InjectionPoint ip) |
| { |
| return super.getUntypedPropertyValue(ip, ip.getType()); |
| } |
| } |
| |
| @Typed |
| private static final class DynamicBean<T> implements Bean<T> |
| { |
| private final Bean<T> producer; |
| private final Set<Type> types; |
| |
| private DynamicBean(final Bean<T> producer, final Set<Type> types) |
| { |
| this.producer = producer; |
| this.types = types; |
| } |
| |
| @Override |
| public Set<Type> getTypes() |
| { |
| return types; |
| } |
| |
| @Override |
| public Set<Annotation> getQualifiers() |
| { |
| return producer.getQualifiers(); |
| } |
| |
| @Override |
| public Class<? extends Annotation> getScope() |
| { |
| return producer.getScope(); |
| } |
| |
| @Override |
| public String getName() |
| { |
| return producer.getName(); |
| } |
| |
| @Override |
| public boolean isNullable() |
| { |
| return producer.isNullable(); |
| } |
| |
| @Override |
| public Set<InjectionPoint> getInjectionPoints() |
| { |
| return producer.getInjectionPoints(); |
| } |
| |
| @Override |
| public Class<?> getBeanClass() |
| { |
| return producer.getBeanClass(); |
| } |
| |
| @Override |
| public Set<Class<? extends Annotation>> getStereotypes() |
| { |
| return producer.getStereotypes(); |
| } |
| |
| @Override |
| public boolean isAlternative() |
| { |
| return producer.isAlternative(); |
| } |
| |
| @Override |
| public T create(final CreationalContext<T> creationalContext) |
| { |
| return producer.create(creationalContext); |
| } |
| |
| @Override |
| public void destroy(final T t, final CreationalContext<T> creationalContext) |
| { |
| producer.destroy(t, creationalContext); |
| } |
| } |
| } |