http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/cdi-se/pom.xml ---------------------------------------------------------------------- diff --git a/modules/injection/cdi-se/pom.xml b/modules/injection/cdi-se/pom.xml new file mode 100644 index 0000000..848b95e --- /dev/null +++ b/modules/injection/cdi-se/pom.xml @@ -0,0 +1,212 @@ +<!-- +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 current 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. +--> +<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.apache.tamaya.ext</groupId> + <artifactId>tamaya-injection-all</artifactId> + <version>0.3-incubating-SNAPSHOT</version> + <relativePath>..</relativePath> + </parent> + + <artifactId>tamaya-cdi-se</artifactId> + <groupId>tamaya-integration</groupId> + <name>Apache Tamaya Modules - Injection CDI (Direct)</name> + <packaging>bundle</packaging> + + <properties> + <owb.version>1.6.2</owb.version> + <weld.version>2.2.7.Final</weld.version> + <geronimo-jcdi-1.1-spec.version>1.0</geronimo-jcdi-1.1-spec.version> + <geronimo-interceptor-1.2-spec.version>1.0</geronimo-interceptor-1.2-spec.version> + <geronimo-atinject-1.0-spec.version>1.0</geronimo-atinject-1.0-spec.version> + <bval.version>0.5</bval.version> + <ds.version>1.1.0</ds.version> + </properties> + + <build> + <plugins> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <executions> + <execution> + <id>prepare-agent</id> + <goals> + <goal>prepare-agent</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + <configuration> + <instructions> + <Export-Package> + org.apache.tamaya.integration.cdi.config + </Export-Package> + <Private-Package> + org.apache.tamaya.integration.cdi.internal + </Private-Package> + </instructions> + </configuration> + </plugin> + </plugins> + </build> + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>java-hamcrest</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.apache.tamaya.ext</groupId> + <artifactId>tamaya-classloader-support</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.tamaya.ext</groupId> + <artifactId>tamaya-spisupport</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.tamaya</groupId> + <artifactId>tamaya-core</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.tamaya</groupId> + <artifactId>tamaya-api</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.tamaya.ext</groupId> + <artifactId>tamaya-injection</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.geronimo.specs</groupId> + <artifactId>geronimo-jcdi_1.1_spec</artifactId> + <version>${geronimo-jcdi-1.1-spec.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.deltaspike.modules</groupId> + <artifactId>deltaspike-test-control-module-api</artifactId> + <version>${ds.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.deltaspike.modules</groupId> + <artifactId>deltaspike-test-control-module-impl</artifactId> + <version>${ds.version}</version> + <scope>test</scope> + </dependency> + </dependencies> + + <profiles> + <profile> + <id>OWB</id> + <activation> + <activeByDefault>true</activeByDefault> + </activation> + <dependencies> + <!-- OWB specific dependencies--> + <dependency> + <groupId>org.apache.geronimo.specs</groupId> + <artifactId>geronimo-atinject_1.0_spec</artifactId> + <version>${geronimo-atinject-1.0-spec.version}</version> + </dependency> + <dependency> + <groupId>org.apache.geronimo.specs</groupId> + <artifactId>geronimo-interceptor_1.2_spec</artifactId> + <version>${geronimo-interceptor-1.2-spec.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.geronimo.specs</groupId> + <artifactId>geronimo-annotation_1.2_spec</artifactId> + <version>1.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.geronimo.specs</groupId> + <artifactId>geronimo-el_2.2_spec</artifactId> + <version>1.0.2</version> + </dependency> + + <dependency> + <groupId>org.apache.openwebbeans</groupId> + <artifactId>openwebbeans-impl</artifactId> + <version>${owb.version}</version> + </dependency> + <dependency> + <groupId>org.apache.openwebbeans</groupId> + <artifactId>openwebbeans-spi</artifactId> + <version>${owb.version}</version> + </dependency> + <dependency> + <groupId>org.apache.openwebbeans</groupId> + <artifactId>openwebbeans-resource</artifactId> + <version>${owb.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.bval</groupId> + <artifactId>bval-jsr303</artifactId> + <version>${bval.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.deltaspike.cdictrl</groupId> + <artifactId>deltaspike-cdictrl-owb</artifactId> + <version>${ds.version}</version> + <scope>test</scope> + </dependency> + </dependencies> + </profile> + <profile> + <id>Weld</id> + <dependencies> + <dependency> + <groupId>org.jboss.weld.se</groupId> + <artifactId>weld-se</artifactId> + <version>${weld.version}</version> + </dependency> + <dependency> + <groupId>org.apache.deltaspike.cdictrl</groupId> + <artifactId>deltaspike-cdictrl-weld</artifactId> + <version>${ds.version}</version> + <scope>test</scope> + </dependency> + </dependencies> + </profile> + </profiles> +</project>
http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/CDIAwareServiceContext.java ---------------------------------------------------------------------- diff --git a/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/CDIAwareServiceContext.java b/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/CDIAwareServiceContext.java new file mode 100644 index 0000000..566d00e --- /dev/null +++ b/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/CDIAwareServiceContext.java @@ -0,0 +1,169 @@ +/* + * 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.tamaya.integration.cdi; + +import org.apache.tamaya.ConfigException; +import org.apache.tamaya.spi.ServiceContext; + +import javax.annotation.Priority; +import javax.enterprise.inject.Instance; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; +import java.text.MessageFormat; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + + +/** + * <p>This class implements a {@link ServiceContext}, which basically provides a similar loading mechanism as used + * by the {@link java.util.ServiceLoader}. Whereas the {@link java.util.ServiceLoader} only loads configurations + * and instances from one classloader, this loader manages configs found and the related instances for each + * classloader along the classloader hierarchies individually. It ensures instances are loaded on the classloader + * level, where they first are visible. Additionally it ensures the same configuration resource (and its + * declared services) are loaded multiple times, when going up the classloader hierarchy.</p> + * + * <p>Finally classloaders are not stored by reference by this class, to ensure they still can be garbage collected. + * Refer also the inherited parent class for further details.</p> + * + * <p>This class uses an ordinal of {@code 10}, so it overrides any default {@link ServiceContext} implementations + * provided with the Tamaya core modules.</p> + */ +public class CDIAwareServiceContext implements ServiceContext { + + /** + * Singletons. + */ + private final Map<Class<?>, Object> singletons = new ConcurrentHashMap<>(); + + private ServiceContext defaultServiceContext = new ServiceLoaderServiceContext(); + + + @Override + public <T> T getService(Class<T> serviceType) { + Object cached = singletons.get(serviceType); + if (cached == null) { + Collection<T> services = getServices(serviceType); + if (services.isEmpty()) { + cached = null; + } else { + cached = getServiceWithHighestPriority(services, serviceType); + } + if(cached!=null) { + singletons.put(serviceType, cached); + } + } + return serviceType.cast(cached); + } + + /** + * Loads and registers services. + * + * @param <T> the concrete type. + * @param serviceType The service type. + * @return the items found, never {@code null}. + */ + @Override + public <T> List<T> getServices(final Class<T> serviceType) { + List<T> found = defaultServiceContext.getServices(serviceType); + BeanManager beanManager = TamayaCDIIntegration.getBeanManager(); + Instance<T> cdiInstances = null; + if(beanManager!=null){ + Set<Bean<?>> instanceBeans = beanManager.getBeans(Instance.class); + Bean<?> bean = instanceBeans.iterator().next(); + cdiInstances = (Instance<T>)beanManager.getReference(bean, Instance.class, + beanManager.createCreationalContext(bean)); + } + if(cdiInstances!=null){ + for(T t:cdiInstances.select(serviceType)){ + found.add(t); + } + } + return found; + } + + /** + * Checks the given instance for a @Priority annotation. If present the annotation's value s evaluated. If no such + * annotation is present, a default priority is returned (1); + * @param o the instance, not null. + * @return a priority, by default 1. + */ + public static int getPriority(Object o){ + int prio = 1; //X TODO discuss default priority + Priority priority = o.getClass().getAnnotation(Priority.class); + if (priority != null) { + prio = priority.value(); + } + return prio; + } + + /** + * @param services to scan + * @param <T> type of the service + * + * @return the service with the highest {@link javax.annotation.Priority#value()} + * + * @throws ConfigException if there are multiple service implementations with the maximum priority + */ + private <T> T getServiceWithHighestPriority(Collection<T> services, Class<T> serviceType) { + + // we do not need the priority stuff if the list contains only one element + if (services.size() == 1) { + return services.iterator().next(); + } + + Integer highestPriority = null; + int highestPriorityServiceCount = 0; + T highestService = null; + + for (T service : services) { + int prio = getPriority(service); + if (highestPriority == null || highestPriority < prio) { + highestService = service; + highestPriorityServiceCount = 1; + highestPriority = prio; + } else if (highestPriority == prio) { + highestPriorityServiceCount++; + } + } + + if (highestPriorityServiceCount > 1) { + throw new ConfigException(MessageFormat.format("Found {0} implementations for Service {1} with Priority {2}: {3}", + highestPriorityServiceCount, + serviceType.getName(), + highestPriority, + services)); + } + + return highestService; + } + + /** + * Returns ordinal of 20, overriding defaults as well as the inherited (internally used) CLAwareServiceContext + * instance. + * @return ordinal of 20. + */ + @Override + public int ordinal() { + return 20; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/DefaultConfigurationContext.java ---------------------------------------------------------------------- diff --git a/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/DefaultConfigurationContext.java b/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/DefaultConfigurationContext.java new file mode 100644 index 0000000..ae1f0bf --- /dev/null +++ b/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/DefaultConfigurationContext.java @@ -0,0 +1,296 @@ +/* + * 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.tamaya.integration.cdi; + +import org.apache.tamaya.ConfigurationProvider; +import org.apache.tamaya.TypeLiteral; +import org.apache.tamaya.spi.ConfigurationContext; +import org.apache.tamaya.spi.ConfigurationContextBuilder; +import org.apache.tamaya.spi.PropertyConverter; +import org.apache.tamaya.spi.PropertyFilter; +import org.apache.tamaya.spi.PropertySource; +import org.apache.tamaya.spi.PropertySourceProvider; +import org.apache.tamaya.spi.PropertyValueCombinationPolicy; +import org.apache.tamaya.spi.ServiceContextManager; + +import javax.annotation.Priority; +import javax.enterprise.inject.Vetoed; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.logging.Logger; + +/** + * Default Implementation of a simple ConfigurationContext. + */ +@SuppressWarnings("FieldCanBeLocal") +@Vetoed +public class DefaultConfigurationContext implements ConfigurationContext { + /** The logger used. */ + private final static Logger LOG = Logger.getLogger(DefaultConfigurationContext.class.getName()); + /** + * Cubcomponent handling {@link PropertyConverter} instances. + */ + private final PropertyConverterManager propertyConverterManager = new PropertyConverterManager(); + + /** + * The current unmodifiable list of loaded {@link PropertySource} instances. + */ + private List<PropertySource> immutablePropertySources; + + /** + * The current unmodifiable list of loaded {@link PropertyFilter} instances. + */ + private List<PropertyFilter> immutablePropertyFilters; + + /** + * The overriding policy used when combining PropertySources registered to evalute the final configuration + * values. + */ + private PropertyValueCombinationPolicy propertyValueCombinationPolicy; + + /** + * Lock for internal synchronization. + */ + private final ReentrantReadWriteLock propertySourceLock = new ReentrantReadWriteLock(); + + /** Comparator used for ordering property sources. */ + private final PropertySourceComparator propertySourceComparator = new PropertySourceComparator(); + + /** Comparator used for ordering property filters. */ + private final PropertyFilterComparator propertyFilterComparator = new PropertyFilterComparator(); + + + /** + * The first time the Configuration system gets invoked we do initialize + * all our {@link PropertySource}s and + * {@link PropertyFilter}s which are known at startup. + */ + public DefaultConfigurationContext() { + List<PropertySource> propertySources = new ArrayList<>(); + + // first we load all PropertySources which got registered via java.util.ServiceLoader + propertySources.addAll(ServiceContextManager.getServiceContext().getServices(PropertySource.class)); + + // after that we add all PropertySources which get dynamically registered via their PropertySourceProviders + propertySources.addAll(evaluatePropertySourcesFromProviders()); + + // now sort them according to their ordinal values + Collections.sort(propertySources, new PropertySourceComparator()); + + immutablePropertySources = Collections.unmodifiableList(propertySources); + LOG.info("Registered " + immutablePropertySources.size() + " property sources: " + + immutablePropertySources); + + // as next step we pick up the PropertyFilters pretty much the same way + List<PropertyFilter> propertyFilters = new ArrayList<>(); + propertyFilters.addAll(ServiceContextManager.getServiceContext().getServices(PropertyFilter.class)); + Collections.sort(propertyFilters, new PropertyFilterComparator()); + immutablePropertyFilters = Collections.unmodifiableList(propertyFilters); + LOG.info("Registered " + immutablePropertyFilters.size() + " property filters: " + + immutablePropertyFilters); + + immutablePropertyFilters = Collections.unmodifiableList(propertyFilters); + LOG.info("Registered " + immutablePropertyFilters.size() + " property filters: " + + immutablePropertyFilters); + propertyValueCombinationPolicy = ServiceContextManager.getServiceContext().getService(PropertyValueCombinationPolicy.class); + if(propertyValueCombinationPolicy==null) { + propertyValueCombinationPolicy = PropertyValueCombinationPolicy.DEFAULT_OVERRIDING_COLLECTOR; + } + LOG.info("Using PropertyValueCombinationPolicy: " + propertyValueCombinationPolicy); + } + + DefaultConfigurationContext(ConfigurationContextBuilder builder) { + List<PropertySource> propertySources = new ArrayList<>(); + // first we load all PropertySources which got registered via java.util.ServiceLoader + propertySources.addAll(builder.getPropertySources()); + // now sort them according to their ordinal values + Collections.sort(propertySources, propertySourceComparator); + immutablePropertySources = Collections.unmodifiableList(propertySources); + LOG.info("Registered " + immutablePropertySources.size() + " property sources: " + + immutablePropertySources); + + // as next step we pick up the PropertyFilters pretty much the same way + List<PropertyFilter> propertyFilters = new ArrayList<>(); + propertyFilters.addAll(ServiceContextManager.getServiceContext().getServices(PropertyFilter.class)); + Collections.sort(propertyFilters, propertyFilterComparator); + immutablePropertyFilters = Collections.unmodifiableList(propertyFilters); + LOG.info("Registered " + immutablePropertyFilters.size() + " property filters: " + + immutablePropertyFilters); + + propertyValueCombinationPolicy = ServiceContextManager.getServiceContext().getService(PropertyValueCombinationPolicy.class); + if(propertyValueCombinationPolicy==null){ + propertyValueCombinationPolicy = PropertyValueCombinationPolicy.DEFAULT_OVERRIDING_COLLECTOR; + } + LOG.info("Using PropertyValueCombinationPolicy: " + propertyValueCombinationPolicy); + } + + + + /** + * Pick up all {@link PropertySourceProvider}s and return all the + * {@link PropertySource}s they like to register. + */ + private Collection<? extends PropertySource> evaluatePropertySourcesFromProviders() { + List<PropertySource> propertySources = new ArrayList<>(); + Collection<PropertySourceProvider> propertySourceProviders = ServiceContextManager.getServiceContext().getServices(PropertySourceProvider.class); + for (PropertySourceProvider propertySourceProvider : propertySourceProviders) { + Collection<PropertySource> sources = propertySourceProvider.getPropertySources(); + LOG.finer("PropertySourceProvider " + propertySourceProvider.getClass().getName() + + " provided the following property sources: " + sources); + propertySources.addAll(sources); + } + + return propertySources; + } + + @Override + public void addPropertySources(PropertySource... propertySourcesToAdd) { + Lock writeLock = propertySourceLock.writeLock(); + try { + writeLock.lock(); + List<PropertySource> newPropertySources = new ArrayList<>(this.immutablePropertySources); + newPropertySources.addAll(Arrays.asList(propertySourcesToAdd)); + Collections.sort(newPropertySources, new PropertySourceComparator()); + + this.immutablePropertySources = Collections.unmodifiableList(newPropertySources); + } finally { + writeLock.unlock(); + } + } + + /** + * Comparator used for ordering PropertySources. + */ + private static class PropertySourceComparator implements Comparator<PropertySource>, Serializable { + + private static final long serialVersionUID = 1L; + + /** + * Order property source reversely, the most important come first. + * + * @param source1 the first PropertySource + * @param source2 the second PropertySource + * @return the comparison result. + */ + private int comparePropertySources(PropertySource source1, PropertySource source2) { + if (source1.getOrdinal() < source2.getOrdinal()) { + return -1; + } else if (source1.getOrdinal() > source2.getOrdinal()) { + return 1; + } else { + return source1.getClass().getName().compareTo(source2.getClass().getName()); + } + } + + @Override + public int compare(PropertySource source1, PropertySource source2) { + return comparePropertySources(source1, source2); + } + } + + /** + * Comparator used for ordering PropertyFilters. + */ + private static class PropertyFilterComparator implements Comparator<PropertyFilter>, Serializable{ + + private static final long serialVersionUID = 1L; + + /** + * Compare 2 filters for ordering the filter chain. + * + * @param filter1 the first filter + * @param filter2 the second filter + * @return the comparison result + */ + private int comparePropertyFilters(PropertyFilter filter1, PropertyFilter filter2) { + Priority prio1 = filter1.getClass().getAnnotation(Priority.class); + Priority prio2 = filter2.getClass().getAnnotation(Priority.class); + int ord1 = prio1 != null ? prio1.value() : 0; + int ord2 = prio2 != null ? prio2.value() : 0; + + if (ord1 < ord2) { + return -1; + } else if (ord1 > ord2) { + return 1; + } else { + return filter1.getClass().getName().compareTo(filter2.getClass().getName()); + } + } + + @Override + public int compare(PropertyFilter filter1, PropertyFilter filter2) { + return comparePropertyFilters(filter1, filter2); + } + } + + @Override + public List<PropertySource> getPropertySources() { + return immutablePropertySources; + } + + @Override + public PropertySource getPropertySource(String name) { + for(PropertySource ps:getPropertySources()){ + if(ps.getName().equals(name)){ + return ps; + } + } + return null; + } + + @Override + public <T> void addPropertyConverter(TypeLiteral<T> typeToConvert, PropertyConverter<T> propertyConverter) { + propertyConverterManager.register(typeToConvert, propertyConverter); + LOG.info("Added PropertyConverter: " + propertyConverter.getClass().getName()); + } + + @Override + public Map<TypeLiteral<?>, List<PropertyConverter<?>>> getPropertyConverters() { + return propertyConverterManager.getPropertyConverters(); + } + + @Override + public <T> List<PropertyConverter<T>> getPropertyConverters(TypeLiteral<T> targetType) { + return propertyConverterManager.getPropertyConverters(targetType); + } + + @Override + public List<PropertyFilter> getPropertyFilters() { + return immutablePropertyFilters; + } + + @Override + public PropertyValueCombinationPolicy getPropertyValueCombinationPolicy(){ + return propertyValueCombinationPolicy; + } + + @Override + public ConfigurationContextBuilder toBuilder() { + return ConfigurationProvider.getConfigurationContextBuilder().setContext(this); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/DefaultConfigurationContextBuilder.java ---------------------------------------------------------------------- diff --git a/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/DefaultConfigurationContextBuilder.java b/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/DefaultConfigurationContextBuilder.java new file mode 100644 index 0000000..c2dc83e --- /dev/null +++ b/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/DefaultConfigurationContextBuilder.java @@ -0,0 +1,152 @@ +///* +// * 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.tamaya.integration.cdi; +// +//import org.apache.tamaya.ConfigException; +//import org.apache.tamaya.TypeLiteral; +//import org.apache.tamaya.spi.ConfigurationContext; +//import org.apache.tamaya.spi.ConfigurationContextBuilder; +//import org.apache.tamaya.spi.PropertyConverter; +//import org.apache.tamaya.spi.PropertyFilter; +//import org.apache.tamaya.spi.PropertySource; +//import org.apache.tamaya.spi.PropertyValueCombinationPolicy; +// +//import javax.enterprise.inject.Vetoed; +//import java.util.ArrayList; +//import java.util.Arrays; +//import java.util.Collection; +//import java.util.HashMap; +//import java.util.List; +//import java.util.Map; +//import java.util.Objects; +// +///** +// * Default implementation of {@link ConfigurationContextBuilder}. +// */ +//@Vetoed +//public class DefaultConfigurationContextBuilder implements ConfigurationContextBuilder { +// +// final Map<String, PropertySource> propertySources = new HashMap<>(); +// final List<PropertyFilter> propertyFilters = new ArrayList<>(); +// final Map<TypeLiteral<?>, List<PropertyConverter<?>>> propertyConverters = new HashMap<>(); +// PropertyValueCombinationPolicy combinationPolicy; +// +// @Override +// public ConfigurationContextBuilder setContext(ConfigurationContext context) { +// this.propertySources.clear(); +// for(PropertySource ps:context.getPropertySources()) { +// this.propertySources.put(ps.getName(), ps); +// } +// this.propertyFilters.clear(); +// this.propertyFilters.addAll(context.getPropertyFilters()); +// this.propertyConverters.clear(); +// this.propertyConverters.putAll(context.getPropertyConverters()); +// this.combinationPolicy = context.getPropertyValueCombinationPolicy(); +// return this; +// } +// +// @Override +// public ConfigurationContextBuilder addPropertySources(Collection<PropertySource> propertySourcesToAdd) { +// for(PropertySource ps:propertySourcesToAdd){ +// if(this.propertySources.containsKey(ps.getName())){ +// throw new ConfigException("Duplicate PropertySource: " + ps.getName()); +// } +// } +// for(PropertySource ps:propertySourcesToAdd) { +// this.propertySources.put(ps.getName(), ps); +// } +// return this; +// } +// +// @Override +// public ConfigurationContextBuilder addPropertySources(PropertySource... propertySourcesToAdd) { +// return addPropertySources(Arrays.asList(propertySourcesToAdd)); +// } +// +// @Override +// public ConfigurationContextBuilder removePropertySources(Collection<String> propertySourcesToRemove) { +// for(String key: propertySourcesToRemove){ +// this.propertySources.remove(key); +// } +// return this; +// } +// +// @Override +// public ConfigurationContextBuilder removePropertySources(String... propertySourcesToRemove) { +// return removePropertySources(Arrays.asList(propertySourcesToRemove)); +// } +// +// @Override +// public ConfigurationContextBuilder addPropertyFilters(Collection<PropertyFilter> filters) { +// this.propertyFilters.addAll(filters); +// return this; +// } +// +// @Override +// public ConfigurationContextBuilder addPropertyFilters(PropertyFilter... filters) { +// return addPropertyFilters(Arrays.asList(filters)); +// } +// +// @Override +// public ConfigurationContextBuilder removePropertyFilters(Collection<PropertyFilter> filters) { +// this.propertyFilters.removeAll(filters); +// return this; +// } +// +// @Override +// public ConfigurationContextBuilder removePropertyFilters(PropertyFilter... filters) { +// return removePropertyFilters(Arrays.asList(filters)); +// } +// +// @Override +// public <T> ConfigurationContextBuilder addPropertyConverters(TypeLiteral<T> typeToConvert, PropertyConverter<T> propertyConverter) { +// List<PropertyConverter<?>> converters = this.propertyConverters.get(typeToConvert); +// if(converters==null){ +// converters = new ArrayList<>(); +// this.propertyConverters.put(typeToConvert, converters); +// } +// return this; +// } +// +// @Override +// public ConfigurationContextBuilder removePropertyConverters(TypeLiteral<?> typeToConvert, PropertyConverter<?>... converters) { +// return removePropertyConverters(typeToConvert, Arrays.asList(converters)); +// } +// +// @Override +// public ConfigurationContextBuilder removePropertyConverters(TypeLiteral<?> typeToConvert, Collection<PropertyConverter<?>> converters) { +// List<PropertyConverter<?>> existing = this.propertyConverters.get(typeToConvert); +// if(existing!=null) { +// existing.removeAll(converters); +// } +// return this; +// } +// +// @Override +// public ConfigurationContextBuilder setPropertyValueCombinationPolicy(PropertyValueCombinationPolicy policy) { +// this.combinationPolicy = Objects.requireNonNull(policy); +// return this; +// } +// +// @Override +// public ConfigurationContext build() { +// return new DefaultConfigurationContext(this); +// } +// +//} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/EnumConverter.java ---------------------------------------------------------------------- diff --git a/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/EnumConverter.java b/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/EnumConverter.java new file mode 100644 index 0000000..d6ad1ba --- /dev/null +++ b/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/EnumConverter.java @@ -0,0 +1,71 @@ +/* + * 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.tamaya.integration.cdi; + +import org.apache.tamaya.ConfigException; +import org.apache.tamaya.spi.ConversionContext; +import org.apache.tamaya.spi.PropertyConverter; + +import javax.enterprise.inject.Vetoed; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Locale; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Converter, converting from String to tge given enum type. + * @param <T> the enum type. + */ +@Vetoed +public class EnumConverter<T> implements PropertyConverter<T> { + private final Logger LOG = Logger.getLogger(EnumConverter.class.getName()); + private Class<T> enumType; + private Method factory; + + public EnumConverter(Class<T> enumType) { + if (!Enum.class.isAssignableFrom(enumType)) { + throw new IllegalArgumentException("Not an Enum: " + enumType.getName()); + } + this.enumType = Objects.requireNonNull(enumType); + try { + this.factory = enumType.getMethod("valueOf", String.class); + } catch (NoSuchMethodException e) { + throw new ConfigException("Uncovertible enum type without valueOf method found, please provide a custom " + + "PropertyConverter for: " + enumType.getName()); + } + } + + @Override + public T convert(String value, ConversionContext context) { + context.addSupportedFormats(getClass(), "<enumValue>"); + try { + return (T) factory.invoke(null, value); + } catch (InvocationTargetException | IllegalAccessException e) { + LOG.log(Level.FINEST, "Invalid enum value '" + value + "' for " + enumType.getName(), e); + } + try { + return (T) factory.invoke(null, value.toUpperCase(Locale.ENGLISH)); + } catch (InvocationTargetException | IllegalAccessException e) { + LOG.log(Level.FINEST, "Invalid enum value '" + value + "' for " + enumType.getName(), e); + } + return null; + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/PropertyConverterManager.java ---------------------------------------------------------------------- diff --git a/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/PropertyConverterManager.java b/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/PropertyConverterManager.java new file mode 100644 index 0000000..e0e35f7 --- /dev/null +++ b/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/PropertyConverterManager.java @@ -0,0 +1,427 @@ +/* + * 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.tamaya.integration.cdi; + +import org.apache.tamaya.ConfigException; +import org.apache.tamaya.TypeLiteral; +import org.apache.tamaya.spi.ConversionContext; +import org.apache.tamaya.spi.PropertyConverter; +import org.apache.tamaya.spi.ServiceContextManager; + +import javax.enterprise.inject.Vetoed; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.logging.Logger; + +/** + * Manager that deals with {@link PropertyConverter} instances. + * This class is thread-safe. + */ +@Vetoed +public class PropertyConverterManager { + /** + * The logger used. + */ + private static final Logger LOG = Logger.getLogger(PropertyConverterManager.class.getName()); + /** + * The registered converters. + */ + private final Map<TypeLiteral<?>, List<PropertyConverter<?>>> converters = new ConcurrentHashMap<>(); + /** + * The transitive converters. + */ + private final Map<TypeLiteral<?>, List<PropertyConverter<?>>> transitiveConverters = new ConcurrentHashMap<>(); + /** + * The lock used. + */ + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + private static final Comparator<Object> PRIORITY_COMPARATOR = new Comparator<Object>() { + + @Override + public int compare(Object o1, Object o2) { + int prio = CDIAwareServiceContext.getPriority(o1) - CDIAwareServiceContext.getPriority(o2); + if (prio < 0) { + return 1; + } else if (prio > 0) { + return -1; + } else { + return o1.getClass().getSimpleName().compareTo(o2.getClass().getSimpleName()); + } + } + }; + + /** + * Constructor. + */ + public PropertyConverterManager() { + initConverters(); + } + + /** + * Registers the default converters provided out of the box. + */ + protected void initConverters() { + for (PropertyConverter conv : ServiceContextManager.getServiceContext().getServices(PropertyConverter.class)) { + Type type = TypeLiteral.getGenericInterfaceTypeParameters(conv.getClass(), PropertyConverter.class)[0]; + register(TypeLiteral.of(type), conv); + } + } + + /** + * Registers a ew converter instance. + * + * @param targetType the target type, not null. + * @param converter the converter, not null. + * @param <T> the type. + */ + public <T> void register(TypeLiteral<T> targetType, PropertyConverter<T> converter) { + Objects.requireNonNull(converter); + Lock writeLock = lock.writeLock(); + try { + writeLock.lock(); + List converters = List.class.cast(this.converters.get(targetType)); + List<PropertyConverter<?>> newConverters = new ArrayList<>(); + if (converters != null) { + newConverters.addAll(converters); + } + newConverters.add(converter); + Collections.sort(newConverters, PRIORITY_COMPARATOR); + this.converters.put(targetType, Collections.unmodifiableList(newConverters)); + // evaluate transitive closure for all inherited supertypes and implemented interfaces + // direct implemented interfaces + for (Class<?> ifaceType : targetType.getRawType().getInterfaces()) { + converters = List.class.cast(this.transitiveConverters.get(TypeLiteral.of(ifaceType))); + newConverters = new ArrayList<>(); + if (converters != null) { + newConverters.addAll(converters); + } + newConverters.add(converter); + Collections.sort(newConverters, PRIORITY_COMPARATOR); + this.transitiveConverters.put(TypeLiteral.of(ifaceType), Collections.unmodifiableList(newConverters)); + } + Class<?> superClass = targetType.getRawType().getSuperclass(); + while (superClass != null && !superClass.equals(Object.class)) { + converters = List.class.cast(this.transitiveConverters.get(TypeLiteral.of(superClass))); + newConverters = new ArrayList<>(); + if (converters != null) { + newConverters.addAll(converters); + } + newConverters.add(converter); + Collections.sort(newConverters, PRIORITY_COMPARATOR); + this.transitiveConverters.put(TypeLiteral.of(superClass), Collections.unmodifiableList(newConverters)); + for (Class<?> ifaceType : superClass.getInterfaces()) { + converters = List.class.cast(this.transitiveConverters.get(TypeLiteral.of(ifaceType))); + newConverters = new ArrayList<>(); + if (converters != null) { + newConverters.addAll(converters); + } + newConverters.add(converter); + Collections.sort(newConverters, PRIORITY_COMPARATOR); + this.transitiveConverters.put(TypeLiteral.of(ifaceType), Collections.unmodifiableList(newConverters)); + } + superClass = superClass.getSuperclass(); + } + } finally { + writeLock.unlock(); + } + } + + /** + * Allows to evaluate if a given target type is supported. + * + * @param targetType the target type, not null. + * @return true, if a converter for the given type is registered, or a default one can be created. + */ + public boolean isTargetTypeSupported(TypeLiteral<?> targetType) { + if (converters.containsKey(targetType) || transitiveConverters.containsKey(targetType)) { + return true; + } + return createDefaultPropertyConverter(targetType) != null; + } + + /** + * Get a map of all property converters currently registered. This will not contain the converters that + * may be created, when an instance is adapted, which provides a String constructor or compatible + * factory methods taking a single String instance. + * + * @return the current map of instantiated and registered converters. + * @see #createDefaultPropertyConverter(TypeLiteral) + */ + public Map<TypeLiteral<?>, List<PropertyConverter<?>>> getPropertyConverters() { + Lock readLock = lock.readLock(); + try { + readLock.lock(); + return new HashMap<>(this.converters); + } finally { + readLock.unlock(); + } + } + + /** + * <p>Get the list of all current registered converters for the given target type. + * If not converters are registered, they component tries to create and register a dynamic + * converter based on String costructor or static factory methods available.</p> + * + * <p>The converters provided are of the following type and returned in the following order:</p> + * <ul> + * <li>Converters mapped explicitly to the required target type are returned first, ordered + * by decreasing priority. This means, if explicit converters are registered these are used + * primarly for converting a value.</li> + * <li>The target type of each explicitly registered converter also can be transitively mapped to + * 1) all directly implemented interfaces, 2) all its superclasses (except Object), 3) all the interfaces + * implemented by its superclasses. These groups of transitive converters is returned similarly in the + * order as mentioned, whereas also here a priority based decreasing ordering is applied.</li> + * <li>java.lang wrapper classes and native types are automatically mapped.</li> + * <li>If no explicit converters are registered, for Enum types a default implementation is provided that + * compares the configuration values with the different enum members defined (cases sensitive mapping).</li> + * </ul> + * + * <p>So given that list above directly registered mappings always are tried first, before any transitive mapping + * should be used. Also in all cases @Priority annotations are honored for ordering of the converters in place. + * Transitive conversion is supported for all directly implemented interfaces (including inherited ones) and + * the inheritance hierarchy (exception Object). Super interfaces of implemented interfaces are ignored.</p> + * + * + * @param targetType the target type, not null. + * @param <T> the type class + * @return the ordered list of converters (may be empty for not convertible types). + * @see #createDefaultPropertyConverter(TypeLiteral) + */ + public <T> List<PropertyConverter<T>> getPropertyConverters(TypeLiteral<T> targetType) { + Lock readLock = lock.readLock(); + List<PropertyConverter<T>> converterList = new ArrayList<>(); + List<PropertyConverter<T>> converters; + // direct mapped converters + try { + readLock.lock(); + converters = List.class.cast(this.converters.get(targetType)); + } finally { + readLock.unlock(); + } + if (converters != null) { + converterList.addAll(converters); + } + // transitive converter + try { + readLock.lock(); + converters = List.class.cast(this.transitiveConverters.get(targetType)); + } finally { + readLock.unlock(); + } + if (converters != null) { + converterList.addAll(converters); + } + // handling of java.lang wrapper classes + TypeLiteral<T> boxedType = mapBoxedType(targetType); + if (boxedType != null) { + try { + readLock.lock(); + converters = List.class.cast(this.converters.get(boxedType)); + } finally { + readLock.unlock(); + } + if (converters != null) { + converterList.addAll(converters); + } + } + if (converterList.isEmpty()) { + // adding any converters created on the fly, e.g. for enum types. + PropertyConverter<T> defaultConverter = createDefaultPropertyConverter(targetType); + if (defaultConverter != null) { + register(targetType, defaultConverter); + try { + readLock.lock(); + converters = List.class.cast(this.converters.get(targetType)); + } finally { + readLock.unlock(); + } + } + if (converters != null) { + converterList.addAll(converters); + } + } + return converterList; + } + + /** + * Maps native types to the corresponding boxed types. + * + * @param targetType the native type. + * @param <T> the type + * @return the boxed type, or null. + */ + private <T> TypeLiteral<T> mapBoxedType(TypeLiteral<T> targetType) { + Type parameterType = targetType.getType(); + if (parameterType == int.class) { + return TypeLiteral.class.cast(TypeLiteral.of(Integer.class)); + } + if (parameterType == short.class) { + return TypeLiteral.class.cast(TypeLiteral.of(Short.class)); + } + if (parameterType == byte.class) { + return TypeLiteral.class.cast(TypeLiteral.of(Byte.class)); + } + if (parameterType == long.class) { + return TypeLiteral.class.cast(TypeLiteral.of(Long.class)); + } + if (parameterType == boolean.class) { + return TypeLiteral.class.cast(TypeLiteral.of(Boolean.class)); + } + if (parameterType == char.class) { + return TypeLiteral.class.cast(TypeLiteral.of(Character.class)); + } + if (parameterType == float.class) { + return TypeLiteral.class.cast(TypeLiteral.of(Float.class)); + } + if (parameterType == double.class) { + return TypeLiteral.class.cast(TypeLiteral.of(Double.class)); + } + if (parameterType == int[].class) { + return TypeLiteral.class.cast(TypeLiteral.of(Integer[].class)); + } + if (parameterType == short[].class) { + return TypeLiteral.class.cast(TypeLiteral.of(Short[].class)); + } + if (parameterType == byte[].class) { + return TypeLiteral.class.cast(TypeLiteral.of(Byte[].class)); + } + if (parameterType == long[].class) { + return TypeLiteral.class.cast(TypeLiteral.of(Long[].class)); + } + if (parameterType == boolean.class) { + return TypeLiteral.class.cast(TypeLiteral.of(Boolean.class)); + } + if (parameterType == char[].class) { + return TypeLiteral.class.cast(TypeLiteral.of(Character[].class)); + } + if (parameterType == float[].class) { + return TypeLiteral.class.cast(TypeLiteral.of(Float[].class)); + } + if (parameterType == double[].class) { + return TypeLiteral.class.cast(TypeLiteral.of(Double[].class)); + } + return null; + } + + /** + * Creates a dynamic PropertyConverter for the given target type. + * + * @param targetType the target type + * @param <T> the type class + * @return a new converter, or null. + */ + protected <T> PropertyConverter<T> createDefaultPropertyConverter(final TypeLiteral<T> targetType) { + if (Enum.class.isAssignableFrom(targetType.getRawType())) { + return new EnumConverter<>(targetType.getRawType()); + } + PropertyConverter<T> converter = null; + final Method factoryMethod = getFactoryMethod(targetType.getRawType(), "of", "valueOf", "instanceOf", "getInstance", "from", "fromString", "parse"); + if (factoryMethod != null) { + converter = new PropertyConverter<T>() { + @Override + public T convert(String value, ConversionContext context) { + context.addSupportedFormats(PropertyConverter.class, "static T of(String)", "static T valueOf(String)", + "static T getInstance(String)", "static T from(String)", + "static T fromString(String)", "static T parse(String)"); + try { + if (!Modifier.isStatic(factoryMethod.getModifiers())) { + throw new ConfigException(factoryMethod.toGenericString() + + " is not a static method. Only static " + + "methods can be used as factory methods."); + } + AccessController.doPrivileged(new PrivilegedAction<Object>() { + @Override + public Object run() { + factoryMethod.setAccessible(true); + return null; + } + }); + Object invoke = factoryMethod.invoke(null, value); + return targetType.getRawType().cast(invoke); + } catch (Exception e) { + throw new ConfigException("Failed to decode '" + value + "'", e); + } + } + }; + } + if (converter == null) { + try { + final Constructor<T> constr = targetType.getRawType().getDeclaredConstructor(String.class); + converter = new PropertyConverter<T>() { + @Override + public T convert(String value, ConversionContext context) { + context.addSupportedFormats(PropertyConverter.class, "new T(String)"); + try { + AccessController.doPrivileged(new PrivilegedAction<Object>() { + @Override + public Object run() { + constr.setAccessible(true); + return null; + } + }); + return constr.newInstance(value); + } catch (Exception e) { + throw new ConfigException("Failed to decode '" + value + "'", e); + } + } + }; + } catch (Exception e) { + LOG.finest("Failed to construct instance of type: " + targetType.getRawType().getName() + ": " + e); + } + } + return converter; + } + + /** + * Tries to evaluate a factory method that can be used to create an instance based on a String. + * + * @param type the target type + * @param methodNames the possible static method names + * @return the first method found, or null. + */ + private Method getFactoryMethod(Class<?> type, String... methodNames) { + Method m; + for (String name : methodNames) { + try { + m = type.getDeclaredMethod(name, String.class); + return m; + } catch (NoSuchMethodException | RuntimeException e) { + LOG.finest("No such factory method found on type: " + type.getName() + ", methodName: " + name); + } + } + return null; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/SEInjectorCDIExtension.java ---------------------------------------------------------------------- diff --git a/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/SEInjectorCDIExtension.java b/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/SEInjectorCDIExtension.java new file mode 100644 index 0000000..86e95b0 --- /dev/null +++ b/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/SEInjectorCDIExtension.java @@ -0,0 +1,112 @@ +/* + * 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.tamaya.integration.cdi; + + +import org.apache.tamaya.inject.ConfigurationInjection; +import org.apache.tamaya.inject.api.Config; +import org.apache.tamaya.inject.api.ConfigDefaultSections; + +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.event.Observes; +import javax.enterprise.inject.Vetoed; +import javax.enterprise.inject.spi.AnnotatedType; +import javax.enterprise.inject.spi.Extension; +import javax.enterprise.inject.spi.InjectionPoint; +import javax.enterprise.inject.spi.InjectionTarget; +import javax.enterprise.inject.spi.ProcessInjectionTarget; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Set; + +/** + * CDI portable extension that integrates {@link org.apache.tamaya.inject.ConfigurationInjector} + * with CDI by adding configuration features to CDI (config enable CDI beans). + */ +@Vetoed +public final class SEInjectorCDIExtension implements Extension { + + /** + * Method that injects the values into any configured fields, by wrapping the given + * InjectionTarget. + * @param pit the injection target + * @param <T> the target type + */ + public <T> void initializeConfiguredFields(final @Observes ProcessInjectionTarget<T> pit) { + final AnnotatedType<T> at = pit.getAnnotatedType(); + if (!isConfigured(at.getJavaClass())) { + return; + } + final InjectionTarget<T> it = pit.getInjectionTarget(); + InjectionTarget<T> wrapped = new InjectionTarget<T>() { + @Override + public void inject(T instance, CreationalContext<T> ctx) { + it.inject(instance, ctx); + ConfigurationInjection.getConfigurationInjector().configure(instance); + } + + @Override + public void postConstruct(T instance) { + it.postConstruct(instance); + } + + @Override + public void preDestroy(T instance) { + it.dispose(instance); + } + + @Override + public void dispose(T instance) { + it.dispose(instance); + } + + @Override + public Set<InjectionPoint> getInjectionPoints() { + return it.getInjectionPoints(); + } + + @Override + public T produce(CreationalContext<T> ctx) { + return it.produce(ctx); + } + }; + pit.setInjectionTarget(wrapped); + } + + private boolean isConfigured(Class type) { + if (type.getAnnotation(ConfigDefaultSections.class) != null) { + return true; + } + // if no class level annotation is there we might have field level annotations only + for (Field field : type.getDeclaredFields()) { + if (field.isAnnotationPresent(Config.class)) { + return true; + } + } + // if no class level annotation is there we might have method level annotations only + for (Method method : type.getDeclaredMethods()) { + if(method.isAnnotationPresent(Config.class)) { + return true; + } + } + return false; + } + + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/ServiceLoaderServiceContext.java ---------------------------------------------------------------------- diff --git a/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/ServiceLoaderServiceContext.java b/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/ServiceLoaderServiceContext.java new file mode 100644 index 0000000..5171d91 --- /dev/null +++ b/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/ServiceLoaderServiceContext.java @@ -0,0 +1,151 @@ +/* + * 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.tamaya.integration.cdi; + +import org.apache.tamaya.ConfigException; +import org.apache.tamaya.spi.ServiceContext; + +import javax.annotation.Priority; +import java.text.MessageFormat; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This class implements the (default) {@link ServiceContext} interface and hereby uses the JDK + * {@link ServiceLoader} to load the services required. + */ +final class ServiceLoaderServiceContext implements ServiceContext { + /** + * List current services loaded, per class. + */ + private final ConcurrentHashMap<Class<?>, List<Object>> servicesLoaded = new ConcurrentHashMap<>(); + /** + * Singletons. + */ + private final Map<Class<?>, Object> singletons = new ConcurrentHashMap<>(); + + @Override + public <T> T getService(Class<T> serviceType) { + Object cached = singletons.get(serviceType); + if (cached == null) { + Collection<T> services = getServices(serviceType); + if (services.isEmpty()) { + cached = null; + } else { + cached = getServiceWithHighestPriority(services, serviceType); + } + if(cached!=null) { + singletons.put(serviceType, cached); + } + } + return serviceType.cast(cached); + } + + /** + * Loads and registers services. + * + * @param <T> the concrete type. + * @param serviceType The service type. + * @return the items found, never {@code null}. + */ + @Override + public <T> List<T> getServices(final Class<T> serviceType) { + List<T> found = (List<T>) servicesLoaded.get(serviceType); + if (found != null) { + return found; + } + List<T> services = new ArrayList<>(); + try { + for (T t : ServiceLoader.load(serviceType)) { + services.add(t); + } + services = Collections.unmodifiableList(services); + } catch (Exception e) { + Logger.getLogger(ServiceLoaderServiceContext.class.getName()).log(Level.WARNING, + "Error loading services current type " + serviceType, e); + } + final List<T> previousServices = List.class.cast(servicesLoaded.putIfAbsent(serviceType, (List<Object>) services)); + return previousServices != null ? previousServices : services; + } + + /** + * Checks the given instance for a @Priority annotation. If present the annotation's value s evaluated. If no such + * annotation is present, a default priority is returned (1); + * @param o the instance, not null. + * @return a priority, by default 1. + */ + public static int getPriority(Object o){ + int prio = 1; //X TODO discuss default priority + Priority priority = o.getClass().getAnnotation(Priority.class); + if (priority != null) { + prio = priority.value(); + } + return prio; + } + + /** + * @param services to scan + * @param <T> type of the service + * + * @return the service with the highest {@link Priority#value()} + * + * @throws ConfigException if there are multiple service implementations with the maximum priority + */ + private <T> T getServiceWithHighestPriority(Collection<T> services, Class<T> serviceType) { + + // we do not need the priority stuff if the list contains only one element + if (services.size() == 1) { + return services.iterator().next(); + } + + Integer highestPriority = null; + int highestPriorityServiceCount = 0; + T highestService = null; + + for (T service : services) { + int prio = getPriority(service); + if (highestPriority == null || highestPriority < prio) { + highestService = service; + highestPriorityServiceCount = 1; + highestPriority = prio; + } else if (highestPriority == prio) { + highestPriorityServiceCount++; + } + } + + if (highestPriorityServiceCount > 1) { + throw new ConfigException(MessageFormat.format("Found {0} implementations for Service {1} with Priority {2}: {3}", + highestPriorityServiceCount, + serviceType.getName(), + highestPriority, + services)); + } + + return highestService; + } + + @Override + public int ordinal() { + return 1; + } + + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/TamayaCDIIntegration.java ---------------------------------------------------------------------- diff --git a/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/TamayaCDIIntegration.java b/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/TamayaCDIIntegration.java new file mode 100644 index 0000000..3f62039 --- /dev/null +++ b/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/TamayaCDIIntegration.java @@ -0,0 +1,52 @@ +/* + * 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.tamaya.integration.cdi; + +import javax.enterprise.event.Observes; +import javax.enterprise.inject.spi.AfterDeploymentValidation; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.Extension; + +/** + * Tamaya main integration with CDI, storing the BeanManager reference for implementation, where no + * JNDI is available or {@code java:comp/env/BeanManager} is not set correctly. + */ +public class TamayaCDIIntegration implements Extension { + /** The BeanManager references stored. */ + private static BeanManager beanManager; + + /** + * Initializes the current BeanManager with the instance passed. + * @param validation the event + * @param beanManager the BeanManager instance + */ + @SuppressWarnings("all") + public void initBeanManager(@Observes AfterDeploymentValidation validation, BeanManager beanManager){ + TamayaCDIIntegration.beanManager = beanManager; + } + + /** + * Get the current {@link BeanManager} instance. + * @return the currently used bean manager. + */ + public static BeanManager getBeanManager(){ + return beanManager; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/TamayaConfigProvider.java ---------------------------------------------------------------------- diff --git a/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/TamayaConfigProvider.java b/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/TamayaConfigProvider.java new file mode 100644 index 0000000..a81e545 --- /dev/null +++ b/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/TamayaConfigProvider.java @@ -0,0 +1,55 @@ +/* + * 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.tamaya.integration.cdi; + + +import org.apache.tamaya.Configuration; +import org.apache.tamaya.ConfigurationProvider; +import org.apache.tamaya.spi.ConfigurationContext; +import org.apache.tamaya.spi.ConfigurationContextBuilder; +import org.apache.tamaya.spisupport.DefaultConfiguration; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; +import javax.inject.Singleton; + +/** + * Tamaya main integreation with CDI (singleton) serving producers for Configuration, ConfigurationContext and + * ConfigurationContextBuilder. + */ +@Singleton +public class TamayaConfigProvider{ + + @Produces + @ApplicationScoped + public Configuration getConfiguration(ConfigurationContext context){ + return new DefaultConfiguration(context); + } + + @Produces @ApplicationScoped + public ConfigurationContext getConfigurationContext(){ + return new DefaultConfigurationContext(); + } + + @Produces + public ConfigurationContextBuilder getConfigurationContextBuilder(){ + return ConfigurationProvider.getConfigurationContextBuilder(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/config/ConfiguredVetoExtension.java ---------------------------------------------------------------------- diff --git a/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/config/ConfiguredVetoExtension.java b/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/config/ConfiguredVetoExtension.java new file mode 100644 index 0000000..f90998d --- /dev/null +++ b/modules/injection/cdi-se/src/main/java/org/apache/tamaya/integration/cdi/config/ConfiguredVetoExtension.java @@ -0,0 +1,42 @@ +/* + * 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.tamaya.integration.cdi.config; + +import org.apache.tamaya.ConfigurationProvider; + +import javax.enterprise.event.Observes; +import javax.enterprise.inject.spi.ProcessAnnotatedType; + +/** + * CDI Extension that can be used to veto on beans by configuring the fully qualified class names (as regex expression) + * under {@code javax.enterprise.inject.vetoed}. Multiple expression can be added as comma separated values. + */ +public class ConfiguredVetoExtension { + + public void observesBean(@Observes ProcessAnnotatedType<?> type){ + String vetoedTypesVal = ConfigurationProvider.getConfiguration().get("javax.enterprise.inject.vetoed"); + String[] vetoedTypes = vetoedTypesVal.split(","); + for(String typeExpr:vetoedTypes){ + if(type.getAnnotatedType().getJavaClass().getName().matches(typeExpr)){ + type.veto(); + } + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/cdi-se/src/main/resources/META-INF/beans.xml ---------------------------------------------------------------------- diff --git a/modules/injection/cdi-se/src/main/resources/META-INF/beans.xml b/modules/injection/cdi-se/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000..adee378 --- /dev/null +++ b/modules/injection/cdi-se/src/main/resources/META-INF/beans.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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 current 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. +--> +<beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://jboss.org/schema/cdi/beans_1_0.xsd"> + +</beans> + http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/cdi-se/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension ---------------------------------------------------------------------- diff --git a/modules/injection/cdi-se/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/modules/injection/cdi-se/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension new file mode 100644 index 0000000..8580a42 --- /dev/null +++ b/modules/injection/cdi-se/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension @@ -0,0 +1,20 @@ +# +# 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 current 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. +# +org.apache.tamaya.integration.cdi.TamayaCDIIntegration +org.apache.tamaya.integration.cdi.SEInjectorCDIExtension http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/cdi-se/src/main/resources/META-INF/services/org.apache.tamaya.spi.ServiceContext ---------------------------------------------------------------------- diff --git a/modules/injection/cdi-se/src/main/resources/META-INF/services/org.apache.tamaya.spi.ServiceContext b/modules/injection/cdi-se/src/main/resources/META-INF/services/org.apache.tamaya.spi.ServiceContext new file mode 100644 index 0000000..4fe7e01 --- /dev/null +++ b/modules/injection/cdi-se/src/main/resources/META-INF/services/org.apache.tamaya.spi.ServiceContext @@ -0,0 +1,19 @@ +# +# 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 current 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. +# +org.apache.tamaya.integration.cdi.CDIAwareServiceContext \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/cdi-se/src/test/java/org/apache/tamaya/integration/cdi/ConfiguredClass.java ---------------------------------------------------------------------- diff --git a/modules/injection/cdi-se/src/test/java/org/apache/tamaya/integration/cdi/ConfiguredClass.java b/modules/injection/cdi-se/src/test/java/org/apache/tamaya/integration/cdi/ConfiguredClass.java new file mode 100644 index 0000000..9d920bc --- /dev/null +++ b/modules/injection/cdi-se/src/test/java/org/apache/tamaya/integration/cdi/ConfiguredClass.java @@ -0,0 +1,112 @@ +/* + * + * 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 current 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.tamaya.integration.cdi; + +import org.apache.tamaya.inject.api.Config; + +import java.math.BigDecimal; + +import javax.inject.Singleton; + +/** + * Class to be loaded from CDI to ensure fields are correctly configured using CDI injection mechanisms. + */ +@Singleton +public class ConfiguredClass{ + + @Config + private String testProperty; + + @Config(value = {"a.b.c.key1","a.b.c.key2","a.b.c.key3"}, defaultValue = "The current \\${JAVA_HOME} env property is ${env:JAVA_HOME}.") + String value1; + + @Config({"foo","a.b.c.key2"}) + private String value2; + + @Config(defaultValue = "N/A") + private String runtimeVersion; + + @Config(defaultValue = "${sys:java.version}") + private String javaVersion2; + + @Config(defaultValue = "5") + private Integer int1; + + @Config + private int int2; + + @Config + private boolean booleanT; + + @Config("BD") + private BigDecimal bigNumber; + + @Config("double1") + private double doubleValue; + + public String getTestProperty() { + return testProperty; + } + + public String getValue1() { + return value1; + } + + public String getValue2() { + return value2; + } + + public String getRuntimeVersion() { + return runtimeVersion; + } + + public String getJavaVersion2() { + return javaVersion2; + } + + public Integer getInt1() { + return int1; + } + + public int getInt2() { + return int2; + } + + public boolean isBooleanT() { + return booleanT; + } + + public BigDecimal getBigNumber() { + return bigNumber; + } + + public double getDoubleValue() { + return doubleValue; + } + + @Override + public String toString(){ + return super.toString() + ": testProperty="+testProperty+", value1="+value1+", value2="+value2 + +", int1="+int1+", int2="+int2+", booleanT="+booleanT+", bigNumber="+bigNumber + +", runtimeVersion="+runtimeVersion+", javaVersion2="+javaVersion2; + } + +}