You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tamaya.apache.org by an...@apache.org on 2017/03/19 22:05:50 UTC
[12/12] incubator-tamaya-extensions git commit: TAMAYA-238: Fxed
tests.
TAMAYA-238: Fxed tests.
Project: http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/commit/346a4f38
Tree: http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/tree/346a4f38
Diff: http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/diff/346a4f38
Branch: refs/heads/master
Commit: 346a4f38744cff476e8753913d56b34cd368c520
Parents: 9050e02
Author: anatole <an...@apache.org>
Authored: Sun Mar 19 23:05:24 2017 +0100
Committer: anatole <an...@apache.org>
Committed: Sun Mar 19 23:05:24 2017 +0100
----------------------------------------------------------------------
.../javax.enterprise.inject.spi.Extension | 20 -
modules/injection/cdi/bnd.bnd | 4 +-
modules/injection/cdi/pom.xml | 63 ---
.../tamaya/cdi/CDIAwareServiceContext.java | 206 ++++++++
.../apache/tamaya/cdi/CDIConfiguredField.java | 77 +++
.../apache/tamaya/cdi/CDIConfiguredMethod.java | 77 +++
.../apache/tamaya/cdi/CDIConfiguredType.java | 94 ++++
.../tamaya/cdi/ConfigurationExtension.java | 290 +++++++++++
.../tamaya/cdi/ConfigurationProducer.java | 162 ++++++
.../apache/tamaya/cdi/DefaultDynamicValue.java | 497 +++++++++++++++++++
.../tamaya/cdi/ServiceLoaderServiceContext.java | 190 +++++++
.../apache/tamaya/cdi/TamayaCDIAccessor.java | 52 ++
.../tamaya/cdi/TamayaCDIInjectionExtension.java | 277 +++++++++++
.../tamaya/cdi/TamayaSEInjectionExtension.java | 109 ++++
.../cdi/extra/ConfiguredVetoExtension.java | 43 ++
.../integration/cdi/CDIAwareServiceContext.java | 206 --------
.../integration/cdi/CDIConfiguredField.java | 77 ---
.../integration/cdi/CDIConfiguredMethod.java | 77 ---
.../integration/cdi/CDIConfiguredType.java | 94 ----
.../integration/cdi/ConfigurationExtension.java | 290 -----------
.../integration/cdi/ConfigurationProducer.java | 253 ----------
.../integration/cdi/DefaultDynamicValue.java | 497 -------------------
.../cdi/ServiceLoaderServiceContext.java | 190 -------
.../integration/cdi/TamayaCDIAccessor.java | 52 --
.../cdi/TamayaCDIInjectionExtension.java | 277 -----------
.../cdi/TamayaSEInjectionExtension.java | 108 ----
.../cdi/extra/ConfiguredVetoExtension.java | 43 --
.../javax.enterprise.inject.spi.Extension | 6 +-
.../org.apache.tamaya.spi.ServiceContext | 2 +-
...onfigurationProducerFailedInjectionTest.java | 33 ++
.../tamaya/cdi/ConfigurationProducerTest.java | 171 +++++++
.../tamaya/cdi/ConfigurationResolverTest.java | 112 +++++
.../org/apache/tamaya/cdi/ConfiguredClass.java | 111 +++++
.../org/apache/tamaya/cdi/ConfiguredTest.java | 83 ++++
.../org/apache/tamaya/cdi/EnvironmentsTest.java | 83 ++++
.../org/apache/tamaya/cdi/InjectedClass.java | 62 +++
.../apache/tamaya/cdi/InterpolationTest.java | 62 +++
.../apache/tamaya/cdi/NotFoundNoDefault.java | 78 +++
.../tamaya/cdi/cfg/ProvidedPropertySource.java | 66 +++
.../tamaya/cdi/cfg/TestConfigProvider.java | 45 ++
.../tamaya/cdi/cfg/TestPropertySource.java | 82 +++
...onfigurationProducerFailedInjectionTest.java | 33 --
.../cdi/ConfigurationProducerTest.java | 169 -------
.../cdi/ConfigurationResolverTest.java | 112 -----
.../tamaya/integration/cdi/ConfiguredClass.java | 111 -----
.../tamaya/integration/cdi/ConfiguredTest.java | 83 ----
.../integration/cdi/EnvironmentsTest.java | 83 ----
.../tamaya/integration/cdi/InjectedClass.java | 62 ---
.../integration/cdi/InterpolationTest.java | 62 ---
.../integration/cdi/NotFoundNoDefault.java | 78 ---
.../cdi/cfg/ProvidedPropertySource.java | 66 ---
.../integration/cdi/cfg/TestConfigProvider.java | 45 --
.../integration/cdi/cfg/TestPropertySource.java | 82 ---
.../javax.enterprise.inject.spi.Extension | 20 +
54 files changed, 3088 insertions(+), 3239 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/346a4f38/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
deleted file mode 100644
index 8580a42..0000000
--- a/modules/injection/cdi-se/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension
+++ /dev/null
@@ -1,20 +0,0 @@
-#
-# 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/346a4f38/modules/injection/cdi/bnd.bnd
----------------------------------------------------------------------
diff --git a/modules/injection/cdi/bnd.bnd b/modules/injection/cdi/bnd.bnd
index a97720d..08b9719 100644
--- a/modules/injection/cdi/bnd.bnd
+++ b/modules/injection/cdi/bnd.bnd
@@ -1,3 +1,3 @@
Export-Package: \
- org.apache.tamaya.inject
-Bundle-SymbolicName: org.apache.tamaya.inject-ee
\ No newline at end of file
+ org.apache.tamaya.cdi
+Bundle-SymbolicName: org.apache.tamaya.cdi
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/346a4f38/modules/injection/cdi/pom.xml
----------------------------------------------------------------------
diff --git a/modules/injection/cdi/pom.xml b/modules/injection/cdi/pom.xml
index 24e4ef0..a0415b1 100644
--- a/modules/injection/cdi/pom.xml
+++ b/modules/injection/cdi/pom.xml
@@ -126,69 +126,6 @@ under the License.
</dependency>
</dependencies>
<profiles>
- <!--<profile>-->
- <!--<id>OWB</id>-->
- <!--<!– there is an issue with this profile:-->
- <!--java.lang.NoClassDefFoundError: org/apache/webbeans/event/EventMetadata-->
- <!--–>-->
- <!--<activation>-->
- <!--<activeByDefault>false</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>test</scope>-->
- <!--</dependency>-->
- <!--<dependency>-->
- <!--<groupId>org.apache.geronimo.specs</groupId>-->
- <!--<artifactId>geronimo-annotation_1.2_spec</artifactId>-->
- <!--<version>1.0</version>-->
- <!--<scope>test</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>
<activation>
http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/346a4f38/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/CDIAwareServiceContext.java
----------------------------------------------------------------------
diff --git a/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/CDIAwareServiceContext.java b/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/CDIAwareServiceContext.java
new file mode 100644
index 0000000..f367f71
--- /dev/null
+++ b/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/CDIAwareServiceContext.java
@@ -0,0 +1,206 @@
+/*
+ * 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.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.io.IOException;
+import java.net.URL;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+
+/**
+ * <p>This class implements a {@link ServiceContext}, which basically provides a similar loading mechanism as used
+ * by the {@link ServiceLoader}. Whereas the {@link 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);
+ }
+
+ @Override
+ public <T> T create(Class<T> serviceType) {
+ T serv = getService(serviceType);
+ if(serv!=null){
+ try {
+ return (T)serv.getClass().newInstance();
+ } catch (Exception e) {
+ Logger.getLogger(getClass().getName())
+ .log(Level.SEVERE, "Failed to create new instance of: " +serviceType.getName(), e);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 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 = TamayaCDIAccessor.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;
+ }
+
+ @Override
+ public Enumeration<URL> getResources(String resource, ClassLoader cl) throws IOException {
+ if(cl==null){
+ cl = Thread.currentThread().getContextClassLoader();
+ }
+ if(cl==null){
+ cl = getClass().getClassLoader();
+ }
+ return cl.getResources(resource);
+ }
+
+ @Override
+ public URL getResource(String resource, ClassLoader cl) {
+ if(cl==null){
+ cl = Thread.currentThread().getContextClassLoader();
+ }
+ if(cl==null){
+ cl = getClass().getClassLoader();
+ }
+ return cl.getResource(resource);
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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/346a4f38/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/CDIConfiguredField.java
----------------------------------------------------------------------
diff --git a/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/CDIConfiguredField.java b/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/CDIConfiguredField.java
new file mode 100644
index 0000000..3331d9a
--- /dev/null
+++ b/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/CDIConfiguredField.java
@@ -0,0 +1,77 @@
+/*
+ * 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.cdi;
+
+import org.apache.tamaya.Configuration;
+import org.apache.tamaya.inject.spi.ConfiguredField;
+
+import javax.enterprise.inject.spi.InjectionPoint;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * CDI implementation for event publishing of configured instances.
+ */
+class CDIConfiguredField implements ConfiguredField{
+
+ private final Field field;
+ private List<String> keys = new ArrayList<>();
+
+ CDIConfiguredField(InjectionPoint injectionPoint, List<String> keys){
+ this.field = (Field)injectionPoint.getMember();
+ this.keys.addAll(keys);
+ this.keys = Collections.unmodifiableList(this.keys);
+ }
+
+ @Override
+ public Class<?> getType() {
+ return field.getType();
+ }
+
+ @Override
+ public Collection<String> getConfiguredKeys() {
+ return keys;
+ }
+
+ @Override
+ public Field getAnnotatedField() {
+ return field;
+ }
+
+ @Override
+ public String getName() {
+ return field.getName();
+ }
+
+ @Override
+ public String getSignature() {
+ return getName()+':'+field.getType().getName();
+ }
+
+ @Override
+ public void configure(Object instance, Configuration config) {
+ throw new UnsupportedOperationException("Use CDI annotations for configuration injection.");
+ }
+
+ @Override
+ public String toString() {
+ return "CDIConfiguredField["+getSignature()+']';
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/346a4f38/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/CDIConfiguredMethod.java
----------------------------------------------------------------------
diff --git a/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/CDIConfiguredMethod.java b/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/CDIConfiguredMethod.java
new file mode 100644
index 0000000..e7f30f5
--- /dev/null
+++ b/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/CDIConfiguredMethod.java
@@ -0,0 +1,77 @@
+/*
+ * 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.cdi;
+
+import org.apache.tamaya.Configuration;
+import org.apache.tamaya.inject.spi.ConfiguredMethod;
+
+import javax.enterprise.inject.spi.InjectionPoint;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Implementation of a configured methods for CDI module.
+ */
+public class CDIConfiguredMethod implements ConfiguredMethod{
+
+ private final Method method;
+ private List<String> keys = new ArrayList<>();
+
+ CDIConfiguredMethod(InjectionPoint injectionPoint, List<String> keys){
+ this.method = (Method)injectionPoint.getMember();
+ this.keys.addAll(keys);
+ this.keys = Collections.unmodifiableList(this.keys);
+ }
+
+ @Override
+ public Collection<String> getConfiguredKeys() {
+ return keys;
+ }
+
+ @Override
+ public Class<?>[] getParameterTypes() {
+ return method.getParameterTypes();
+ }
+
+ @Override
+ public Method getAnnotatedMethod() {
+ return method;
+ }
+
+ @Override
+ public String getName() {
+ return method.getName();
+ }
+
+ @Override
+ public String getSignature() {
+ return null;
+ }
+
+ @Override
+ public void configure(Object instance, Configuration config) {
+ throw new UnsupportedOperationException("Use CDI annotations for configuration injection.");
+ }
+
+ @Override
+ public String toString() {
+ return "CDIConfiguredMethod["+getSignature()+']';
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/346a4f38/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/CDIConfiguredType.java
----------------------------------------------------------------------
diff --git a/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/CDIConfiguredType.java b/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/CDIConfiguredType.java
new file mode 100644
index 0000000..901b88f
--- /dev/null
+++ b/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/CDIConfiguredType.java
@@ -0,0 +1,94 @@
+/*
+ * 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.cdi;
+
+import org.apache.tamaya.Configuration;
+import org.apache.tamaya.inject.spi.ConfiguredField;
+import org.apache.tamaya.inject.spi.ConfiguredMethod;
+import org.apache.tamaya.inject.spi.ConfiguredType;
+
+import javax.enterprise.inject.spi.InjectionPoint;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Event published for items configured by CDI extensions. This is for example used by the documentation module
+ * to automatically track the configuration endpoints for documentation.
+ */
+class CDIConfiguredType implements ConfiguredType{
+
+ private final Class<?> type;
+ private final List<CDIConfiguredMethod> methods = new ArrayList<>();
+ private final List<CDIConfiguredField> fields = new ArrayList<>();
+
+ public CDIConfiguredType(Class<?> type){
+ this.type = Objects.requireNonNull(type);
+ }
+
+ @Override
+ public Class getType() {
+ return type;
+ }
+
+ @Override
+ public String getName() {
+ return type.getName();
+ }
+
+ @Override
+ public Collection<ConfiguredField> getConfiguredFields() {
+ return null;
+ }
+
+ @Override
+ public Collection<ConfiguredMethod> getConfiguredMethods() {
+ return null;
+ }
+
+ @Override
+ public void configure(Object instance, Configuration config) {
+ throw new UnsupportedOperationException("Use CDI annotations for configuration injection.");
+ }
+
+ /**
+ * Used to build up during injection point processing.
+ * @param injectionPoint the CDI injection ppint, not null.
+ * @param keys the possible config keys, in order of precedence, not null.
+ */
+ void addConfiguredMember(InjectionPoint injectionPoint, List<String> keys) {
+ Member member = injectionPoint.getMember();
+ if(member instanceof Field){
+ this.fields.add(new CDIConfiguredField(injectionPoint, keys));
+ } else if(member instanceof Method){
+ this.methods.add(new CDIConfiguredMethod(injectionPoint, keys));
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "CDIConfiguredType{" +
+ "type=" + type +
+ ", methods=" + methods +
+ ", fields=" + fields +
+ '}';
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/346a4f38/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/ConfigurationExtension.java
----------------------------------------------------------------------
diff --git a/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/ConfigurationExtension.java b/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/ConfigurationExtension.java
new file mode 100644
index 0000000..b715b78
--- /dev/null
+++ b/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/ConfigurationExtension.java
@@ -0,0 +1,290 @@
+/*
+ * 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.cdi;
+
+import org.apache.tamaya.ConfigException;
+import org.apache.tamaya.ConfigOperator;
+import org.apache.tamaya.Configuration;
+import org.apache.tamaya.ConfigurationProvider;
+import org.apache.tamaya.inject.api.Config;
+import org.apache.tamaya.inject.api.ConfigDefaultSections;
+import org.apache.tamaya.inject.api.WithConfigOperator;
+import org.apache.tamaya.inject.api.WithPropertyConverter;
+import org.apache.tamaya.spi.PropertyConverter;
+
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.spi.AfterBeanDiscovery;
+import javax.enterprise.inject.spi.Bean;
+import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.inject.spi.ProcessBean;
+import javax.enterprise.inject.spi.ProcessProducerMethod;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Logger;
+
+
+/**
+ * CDI Extension module that adds injection mechanism for configuration.
+ *
+ * @see org.apache.tamaya.inject.api.Config
+ * @see org.apache.tamaya.inject.api.ConfigDefaultSections
+ * @see ConfigException
+ */
+public class ConfigurationExtension implements Extension {
+
+ private static final Logger LOG = Logger.getLogger(ConfigurationExtension.class.getName());
+
+ static final Map<Class, ConfigOperator> CUSTOM_OPERATORS = new ConcurrentHashMap<>();
+ static final Map<Class, PropertyConverter> CUSTOM_CONVERTERS = new ConcurrentHashMap<>();
+
+ private final Set<Type> types = new HashSet<>();
+ private Bean<?> convBean;
+
+ /**
+ * Constructor for loading logging its load.
+ */
+ public ConfigurationExtension(){
+ LOG.finest("Loading Tamaya CDI Support...");
+ }
+
+ /**
+ * Method that checks the configuration injection points during deployment for available configuration.
+ * @param pb the bean to process.
+ * @param beanManager the bean manager to notify about new injections.
+ */
+ public void retrieveTypes(@Observes final ProcessBean<?> pb, BeanManager beanManager) {
+
+ final Set<InjectionPoint> ips = pb.getBean().getInjectionPoints();
+ CDIConfiguredType configuredType = new CDIConfiguredType(pb.getBean().getBeanClass());
+
+ boolean configured = false;
+ boolean logged = false;
+ for (InjectionPoint injectionPoint : ips) {
+ if (injectionPoint.getAnnotated().isAnnotationPresent(Config.class)) {
+ final Config annotation = injectionPoint.getAnnotated().getAnnotation(Config.class);
+ final ConfigDefaultSections typeAnnot = injectionPoint.getAnnotated().getAnnotation(ConfigDefaultSections.class);
+ final List<String> keys = evaluateKeys(injectionPoint.getMember().getName(),
+ annotation!=null?annotation.value():null,
+ typeAnnot!=null?typeAnnot.value():null);
+
+ final WithConfigOperator withOperatorAnnot = injectionPoint.getAnnotated().getAnnotation(WithConfigOperator.class);
+ if(withOperatorAnnot!=null){
+ tryLoadOpererator(withOperatorAnnot.value());
+ }
+ final WithPropertyConverter withConverterAnnot = injectionPoint.getAnnotated().getAnnotation(WithPropertyConverter.class);
+ if(withConverterAnnot!=null){
+ tryLoadConverter(withConverterAnnot.value());
+ }
+
+ // We don't want to wait until the injection really fails at runtime.
+ // If there is a non resolvable configuration, we want to know at startup.
+ Configuration config = ConfigurationProvider.getConfiguration();
+ String value = null;
+ for(String key:keys) {
+ value = config.get(key);
+ if(value!=null){
+ break;
+ }
+ }
+ if(value==null && !annotation.defaultValue().isEmpty()){
+ value = annotation.defaultValue();
+ }
+ if(value==null){
+ throw new ConfigException(String.format(
+ "Cannot resolve any of the possible configuration keys: %s. Please provide one of the given keys " +
+ "with a value in your configuration sources.",
+ keys.toString()));
+ }
+ types.add(injectionPoint.getType());
+ if(annotation!=null){
+ configured = true;
+ if(!logged) {
+ LOG.finest("Enabling Tamaya CDI Configuration on bean: " + configuredType.getName());
+ }
+ configuredType.addConfiguredMember(injectionPoint, keys);
+ }
+ }
+ }
+ if(configured) {
+ beanManager.fireEvent(configuredType);
+ }
+ }
+
+
+ public void captureConvertBean(@Observes final ProcessProducerMethod<?, ?> ppm) {
+ if (ppm.getAnnotated().isAnnotationPresent(Config.class)) {
+ convBean = ppm.getBean();
+ }
+
+ }
+
+ public void addConverter(@Observes final AfterBeanDiscovery abd, final BeanManager bm) {
+ if(!types.isEmpty()) {
+ abd.addBean(new ConverterBean(convBean, types));
+ }
+ }
+
+ private void tryLoadOpererator(Class<? extends ConfigOperator> operatorClass) {
+ Objects.requireNonNull(operatorClass);
+ if(ConfigOperator.class == operatorClass){
+ return;
+ }
+ try{
+ if(!CUSTOM_OPERATORS.containsKey(operatorClass)) {
+ CUSTOM_OPERATORS.put(operatorClass, operatorClass.newInstance());
+ }
+ } catch(Exception e){
+ throw new ConfigException("Custom ConfigOperator could not be loaded: " + operatorClass.getName(), e);
+ }
+ }
+
+ private void tryLoadConverter(Class<? extends PropertyConverter> converterClass) {
+ Objects.requireNonNull(converterClass);
+ if(PropertyConverter.class == converterClass){
+ return;
+ }
+ try{
+ if(!CUSTOM_CONVERTERS.containsKey(converterClass)) {
+ CUSTOM_CONVERTERS.put(converterClass, converterClass.newInstance());
+ }
+ } catch(Exception e){
+ throw new ConfigException("Custom PropertyConverter could not be loaded: " + converterClass.getName(), e);
+ }
+ }
+
+ /**
+ * Evaluates the effective keys to be used. if no {@code keys} are defined, {@code memberName} is used.
+ * The effective keys are then combined with the sections given (if any) and only, if the given keys are not
+ * absolute keys (surrounded by brackets).
+ * @param memberName the default member name, not null.
+ * @param keys the keys, may be empty, or null.
+ * @param sections the default sections, may be empty. May also be null.
+ * @return the list of keys to be finally used for configuration resolution in order of
+ * precedence. The first keys in the list that could be successfully resolved define the final
+ * configuration value.
+ */
+ public static List<String> evaluateKeys(String memberName, String[] keys, String[] sections) {
+ List<String> effKeys = new ArrayList<>();
+ if(keys!=null){
+ effKeys.addAll(Arrays.asList(keys));
+ }
+ if (effKeys.isEmpty()) {
+ effKeys.add(memberName);
+ }
+ ListIterator<String> iterator = effKeys.listIterator();
+ while (iterator.hasNext()) {
+ String next = iterator.next();
+ if (next.startsWith("[") && next.endsWith("]")) {
+ // absolute key, strip away brackets, take key as is
+ iterator.set(next.substring(1, next.length() - 1));
+ } else {
+ if (sections != null && sections.length>0) {
+ // Remove original entry, since it will be replaced with prefixed entries
+ iterator.remove();
+ // Add prefixed entries, including absolute (root) entry for "" area keys.
+ for (String area : sections) {
+ iterator.add(area.isEmpty() ? next : area + '.' + next);
+ }
+ }
+ }
+ }
+ return effKeys;
+ }
+
+
+ /**
+ * Internally used conversion bean.
+ */
+ private static class ConverterBean implements Bean<Object> {
+
+ private final Bean<Object> delegate;
+ private final Set<Type> types;
+
+ public ConverterBean(final Bean convBean, final Set<Type> types) {
+ this.types = types;
+ this.delegate = convBean;
+ }
+
+ @Override
+ public Set<Type> getTypes() {
+ return types;
+ }
+
+ @Override
+ public Class<?> getBeanClass() {
+ return delegate.getBeanClass();
+ }
+
+ @Override
+ public Set<InjectionPoint> getInjectionPoints() {
+ return delegate.getInjectionPoints();
+ }
+
+ @Override
+ public String getName() {
+ return delegate.getName();
+ }
+
+ @Override
+ public Set<Annotation> getQualifiers() {
+ return delegate.getQualifiers();
+ }
+
+ @Override
+ public Class<? extends Annotation> getScope() {
+ return delegate.getScope();
+ }
+
+ @Override
+ public Set<Class<? extends Annotation>> getStereotypes() {
+ return delegate.getStereotypes();
+ }
+
+ @Override
+ public boolean isAlternative() {
+ return delegate.isAlternative();
+ }
+
+ @Override
+ public boolean isNullable() {
+ return delegate.isNullable();
+ }
+
+ @Override
+ public Object create(CreationalContext<Object> creationalContext) {
+ return delegate.create(creationalContext);
+ }
+
+ @Override
+ public void destroy(Object instance, CreationalContext<Object> creationalContext) {
+ delegate.destroy(instance, creationalContext);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/346a4f38/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/ConfigurationProducer.java
----------------------------------------------------------------------
diff --git a/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/ConfigurationProducer.java b/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/ConfigurationProducer.java
new file mode 100644
index 0000000..1d07c98
--- /dev/null
+++ b/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/ConfigurationProducer.java
@@ -0,0 +1,162 @@
+/*
+ * 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.cdi;
+
+import org.apache.tamaya.*;
+import org.apache.tamaya.inject.api.*;
+import org.apache.tamaya.spi.ConfigurationContext;
+import org.apache.tamaya.spi.ConfigurationContextBuilder;
+import org.apache.tamaya.spi.ConversionContext;
+import org.apache.tamaya.spi.PropertyConverter;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Produces;
+import javax.enterprise.inject.spi.InjectionPoint;
+import java.io.File;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Producer bean for configuration properties.
+ */
+@ApplicationScoped
+public class ConfigurationProducer {
+
+ private static final Logger LOGGER = Logger.getLogger(ConfigurationProducer.class.getName());
+
+ private DynamicValue createDynamicValue(final InjectionPoint injectionPoint) {
+ Member member = injectionPoint.getMember();
+ if (member instanceof Field) {
+ return DefaultDynamicValue.of((Field) member, ConfigurationProvider.getConfiguration());
+ } else if (member instanceof Method) {
+ return DefaultDynamicValue.of((Method) member, ConfigurationProvider.getConfiguration());
+ }
+ return null;
+ }
+
+ @Produces
+ @Config
+ public Object resolveAndConvert(final InjectionPoint injectionPoint) {
+ if (DynamicValue.class.equals(injectionPoint.getAnnotated().getBaseType())) {
+ return createDynamicValue(injectionPoint);
+ }
+ final Config annotation = injectionPoint.getAnnotated().getAnnotation(Config.class);
+ final ConfigDefaultSections typeAnnot = injectionPoint.getAnnotated().getAnnotation(ConfigDefaultSections.class);
+ final List<String> keys = TamayaCDIInjectionExtension.evaluateKeys(injectionPoint.getMember().getName(),
+ annotation != null ? annotation.value() : null,
+ typeAnnot != null ? typeAnnot.value() : null);
+
+ final WithConfigOperator withOperatorAnnot = injectionPoint.getAnnotated().getAnnotation(WithConfigOperator.class);
+ ConfigOperator operator = null;
+ if (withOperatorAnnot != null) {
+ operator = TamayaCDIInjectionExtension.CUSTOM_OPERATORS.get(withOperatorAnnot.value());
+ }
+ PropertyConverter customConverter = null;
+ final WithPropertyConverter withConverterAnnot = injectionPoint.getAnnotated().getAnnotation(WithPropertyConverter.class);
+ if (withConverterAnnot != null) {
+ customConverter = TamayaCDIInjectionExtension.CUSTOM_CONVERTERS.get(withConverterAnnot.value());
+ }
+
+ // unless the extension is not installed, this should never happen because the extension
+ // enforces the resolvability of the config
+ Configuration config = ConfigurationProvider.getConfiguration();
+ if (operator != null) {
+ config = operator.operate(config);
+ }
+ final Class<?> toType = (Class<?>) injectionPoint.getAnnotated().getBaseType();
+ String textValue = null;
+ String defaultTextValue = annotation.defaultValue().isEmpty() ? "" : annotation.defaultValue();
+ String keyFound = null;
+ for (String key : keys) {
+ textValue = config.get(key);
+ if (textValue != null) {
+ keyFound = key;
+ break;
+ }
+ }
+ ConversionContext.Builder builder = new ConversionContext.Builder(config,
+ ConfigurationProvider.getConfiguration().getContext(), keyFound, TypeLiteral.of(toType));
+ if (injectionPoint.getMember() instanceof AnnotatedElement) {
+ builder.setAnnotatedElement((AnnotatedElement) injectionPoint.getMember());
+ }
+ ConversionContext conversionContext = builder.build();
+ Object value = null;
+ if (keyFound != null) {
+ if (customConverter != null) {
+ value = customConverter.convert(textValue, conversionContext);
+ }
+ if (value == null) {
+ value = config.get(keyFound, toType);
+ }
+ } else if (defaultTextValue != null) {
+ value = defaultTextValue;
+ if (customConverter != null) {
+ value = customConverter.convert((String)value, conversionContext);
+ }
+ if (value != null) {
+ List<PropertyConverter<Object>> converters = ConfigurationProvider.getConfiguration().getContext()
+ .getPropertyConverters(TypeLiteral.of(toType));
+ for (PropertyConverter<Object> converter : converters) {
+ try {
+ value = converter.convert(defaultTextValue, conversionContext);
+ if (value != null) {
+ LOGGER.log(Level.FINEST, "Parsed default value from '" + defaultTextValue + "' into " +
+ injectionPoint);
+ break;
+ }
+ } catch (Exception e) {
+ LOGGER.log(Level.FINEST, "Failed to convert default value '" + defaultTextValue + "' for " +
+ injectionPoint, e);
+ }
+ }
+ }
+ }
+ if (value == null) {
+ throw new ConfigException(String.format(
+ "Can't resolve any of the possible config keys: %s to the required target type: %s, supported formats: %s",
+ keys.toString(), toType.getName(), conversionContext.getSupportedFormats().toString()));
+ }
+ LOGGER.finest(String.format("Injecting %s for key %s in class %s", keyFound, value.toString(), injectionPoint.toString()));
+ return value;
+ }
+
+ @Produces
+ public Configuration getConfiguration(){
+ return ConfigurationProvider.getConfiguration();
+ }
+
+ @Produces
+ public ConfigurationContext getConfigurationContext(){
+ return ConfigurationProvider.getConfiguration().getContext();
+ }
+
+ @Produces
+ public ConfigurationContextBuilder getConfigurationContextBuilder(){
+ return ConfigurationProvider.getConfigurationContextBuilder();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/346a4f38/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/DefaultDynamicValue.java
----------------------------------------------------------------------
diff --git a/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/DefaultDynamicValue.java b/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/DefaultDynamicValue.java
new file mode 100644
index 0000000..5e4692f
--- /dev/null
+++ b/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/DefaultDynamicValue.java
@@ -0,0 +1,497 @@
+/*
+ * 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.cdi;
+
+import org.apache.tamaya.ConfigException;
+import org.apache.tamaya.Configuration;
+import org.apache.tamaya.TypeLiteral;
+import org.apache.tamaya.inject.api.DynamicValue;
+import org.apache.tamaya.inject.api.LoadPolicy;
+import org.apache.tamaya.inject.api.UpdatePolicy;
+import org.apache.tamaya.inject.api.WithPropertyConverter;
+import org.apache.tamaya.inject.spi.BaseDynamicValue;
+import org.apache.tamaya.inject.spi.InjectionUtils;
+import org.apache.tamaya.spi.ConversionContext;
+import org.apache.tamaya.spi.PropertyConverter;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.*;
+import java.util.logging.Logger;
+
+/**
+ * A accessor for a single configured value. This can be used to support values that may change during runtime,
+ * reconfigured or final. Hereby external code (could be Tamaya configuration listners or client code), can set a
+ * new value. Depending on the {@link UpdatePolicy} the new value is immedeately active or it requires an active commit
+ * by client code. Similarly an instance also can ignore all later changes to the value.
+ * <h3>Implementation Details</h3>
+ * This class is
+ * <ul>
+ * <li>Serializable, when also the item stored is serializable</li>
+ * <li>Thread safe</li>
+ * </ul>
+ *
+ * @param <T> The type of the value.
+ */
+final class DefaultDynamicValue<T> extends BaseDynamicValue<T> {
+
+ private static final long serialVersionUID = -2071172847144537443L;
+
+ /**
+ * The property name of the entry.
+ */
+ private final String propertyName;
+ /**
+ * The keys to be resolved.
+ */
+ private final String[] keys;
+ /**
+ * Back reference to the base configuration instance. This reference is used reevalaute the given property and
+ * compare the result with the previous value after a configuration change was triggered.
+ */
+ private final Configuration configuration;
+ /**
+ * The target type of the property used to lookup a matching {@link PropertyConverter}.
+ * If null, {@code propertyConverter} is set and used instead.
+ */
+ private final TypeLiteral<T> targetType;
+ /**
+ * The property converter to be applied, may be null. In the ladder case targetType is not null.
+ */
+ private final PropertyConverter<T> propertyConverter;
+ /**
+ * Policy that defines how new values are applied, be default it is applied initially once, but never updated
+ * anymore.
+ */
+ private UpdatePolicy updatePolicy;
+ /**
+ * Load policy.
+ */
+ private final LoadPolicy loadPolicy;
+
+ /**
+ * The current value, never null.
+ */
+ private transient T value;
+ /**
+ * The new value, or null.
+ */
+ private transient Object[] newValue;
+ /**
+ * List of listeners that listen for changes.
+ */
+ private transient WeakList<PropertyChangeListener> listeners;
+
+ /**
+ * Constructor.
+ *
+ * @param propertyName the name of the fields' property/method.
+ * @param keys the keys of the property, not null.
+ * @param configuration the configuration, not null.
+ * @param targetType the target type, not null.
+ * @param propertyConverter the optional converter to be used.
+ */
+ private DefaultDynamicValue(String propertyName, Configuration configuration, TypeLiteral<T> targetType,
+ PropertyConverter<T> propertyConverter, List<String> keys, LoadPolicy loadPolicy,
+ UpdatePolicy updatePolicy) {
+ this.propertyName = Objects.requireNonNull(propertyName);
+ this.keys = keys.toArray(new String[keys.size()]);
+ this.configuration = Objects.requireNonNull(configuration);
+ this.propertyConverter = propertyConverter;
+ this.targetType = targetType;
+ this.loadPolicy = Objects.requireNonNull(loadPolicy);
+ this.updatePolicy = Objects.requireNonNull(updatePolicy);
+ if(loadPolicy == LoadPolicy.INITIAL){
+ this.value = evaluateValue();
+ }
+ }
+
+ public static DynamicValue of(Field annotatedField, Configuration configuration) {
+ return of(annotatedField, configuration, LoadPolicy.ALWAYS, UpdatePolicy.IMMEDIATE);
+ }
+
+ public static DynamicValue of(Field annotatedField, Configuration configuration, LoadPolicy loadPolicy) {
+ return of(annotatedField, configuration, loadPolicy, UpdatePolicy.IMMEDIATE);
+ }
+
+ public static DynamicValue of(Field annotatedField, Configuration configuration, UpdatePolicy updatePolicy) {
+ return of(annotatedField, configuration, LoadPolicy.ALWAYS, updatePolicy);
+ }
+
+ public static DynamicValue of(Field annotatedField, Configuration configuration, LoadPolicy loadPolicy, UpdatePolicy updatePolicy) {
+ // Check for adapter/filter
+ Type targetType = annotatedField.getGenericType();
+ if (targetType == null) {
+ throw new ConfigException("Failed to evaluate target type for " + annotatedField.getDeclaringClass().getName()
+ + '.' + annotatedField.getName());
+ }
+ if (targetType instanceof ParameterizedType) {
+ ParameterizedType pt = (ParameterizedType) targetType;
+ Type[] types = pt.getActualTypeArguments();
+ if (types.length != 1) {
+ throw new ConfigException("Failed to evaluate target type for " + annotatedField.getDeclaringClass().getName()
+ + '.' + annotatedField.getName());
+ }
+ targetType = types[0];
+ }
+ PropertyConverter<?> propertyConverter = null;
+ WithPropertyConverter annot = annotatedField.getAnnotation(WithPropertyConverter.class);
+ if (annot != null) {
+ try {
+ propertyConverter = annot.value().newInstance();
+ } catch (Exception e) {
+ throw new ConfigException("Failed to instantiate annotated PropertyConverter on " +
+ annotatedField.getDeclaringClass().getName()
+ + '.' + annotatedField.getName(), e);
+ }
+ }
+ List<String> keys = InjectionUtils.getKeys(annotatedField);
+ return new DefaultDynamicValue(annotatedField.getName(), configuration,
+ TypeLiteral.of(targetType), propertyConverter, keys, loadPolicy, updatePolicy);
+ }
+
+ public static DynamicValue of(Method method, Configuration configuration) {
+ return of(method, configuration, LoadPolicy.ALWAYS, UpdatePolicy.IMMEDIATE);
+ }
+
+ public static DynamicValue of(Method method, Configuration configuration, UpdatePolicy updatePolicy) {
+ return of(method, configuration, LoadPolicy.ALWAYS, updatePolicy);
+ }
+
+ public static DynamicValue of(Method method, Configuration configuration, LoadPolicy loadPolicy) {
+ return of(method, configuration, loadPolicy, UpdatePolicy.IMMEDIATE);
+ }
+
+ public static DynamicValue of(Method method, Configuration configuration, LoadPolicy loadPolicy, UpdatePolicy updatePolicy) {
+ // Check for adapter/filter
+ Type targetType = method.getGenericReturnType();
+ if (targetType == null) {
+ throw new ConfigException("Failed to evaluate target type for " + method.getDeclaringClass()
+ .getName() + '.' + method.getName());
+ }
+ if (targetType instanceof ParameterizedType) {
+ ParameterizedType pt = (ParameterizedType) targetType;
+ Type[] types = pt.getActualTypeArguments();
+ if (types.length != 1) {
+ throw new ConfigException("Failed to evaluate target type for " + method.getDeclaringClass()
+ .getName() + '.' + method.getName());
+ }
+ targetType = types[0];
+ }
+ PropertyConverter<Object> propertyConverter = null;
+ WithPropertyConverter annot = method.getAnnotation(WithPropertyConverter.class);
+ if (annot != null) {
+ try {
+ propertyConverter = (PropertyConverter<Object>) annot.value().newInstance();
+ } catch (Exception e) {
+ throw new ConfigException("Failed to instantiate annotated PropertyConverter on " +
+ method.getDeclaringClass().getName()
+ + '.' + method.getName(), e);
+ }
+ }
+ return new DefaultDynamicValue<>(method.getName(),
+ configuration, TypeLiteral.of(targetType), propertyConverter, InjectionUtils.getKeys(method),
+ loadPolicy, updatePolicy);
+ }
+
+
+ /**
+ * Commits a new value that has not been committed yet, make it the new value of the instance. On change any
+ * registered listeners will be triggered.
+ */
+ public void commit() {
+ T oldValue = value;
+ value = newValue==null?null:(T)newValue[0];
+ newValue = null;
+ informListeners(oldValue, value);
+ }
+
+ private void informListeners(T value, T newValue) {
+ synchronized (this) {
+ PropertyChangeEvent evt = new PropertyChangeEvent(this, propertyName, value,
+ newValue);
+ if (listeners != null) {
+ for (PropertyChangeListener consumer : listeners.get()) {
+ consumer.propertyChange(evt);
+ }
+ }
+ }
+ }
+
+ /**
+ * Discards a new value that was published. No listeners will be informed.
+ */
+ public void discard() {
+ newValue = null;
+ }
+
+
+ /**
+ * Access the {@link UpdatePolicy} used for updating this value.
+ *
+ * @return the update policy, never null.
+ */
+ public UpdatePolicy getUpdatePolicy() {
+ return updatePolicy;
+ }
+
+ /**
+ * Sets a new {@link UpdatePolicy}.
+ *
+ * @param updatePolicy the new policy, not null.
+ */
+ public void setUpdatePolicy(UpdatePolicy updatePolicy) {
+ this.updatePolicy = Objects.requireNonNull(updatePolicy);
+ }
+
+ /**
+ * Add a listener to be called as weak reference, when this value has been changed.
+ *
+ * @param l the listener, not null
+ */
+ public void addListener(PropertyChangeListener l) {
+ if (listeners == null) {
+ listeners = new WeakList<>();
+ }
+ listeners.add(l);
+ }
+
+ /**
+ * Removes a listener to be called, when this value has been changed.
+ *
+ * @param l the listner to be removed, not null
+ */
+ public void removeListener(PropertyChangeListener l) {
+ if (listeners != null) {
+ listeners.remove(l);
+ }
+ }
+
+ /**
+ * If a value is present in this {@code DynamicValue}, returns the value,
+ * otherwise throws {@code ConfigException}.
+ *
+ * @return the non-null value held by this {@code Optional}
+ * @throws ConfigException if there is no value present
+ * @see DefaultDynamicValue#isPresent()
+ */
+ public T get() {
+ T newLocalValue;
+ if(loadPolicy!=LoadPolicy.INITIAL) {
+ newLocalValue = evaluateValue();
+ if (this.value == null) {
+ this.value = newLocalValue;
+ }
+ if(!Objects.equals(this.value, newLocalValue)){
+ switch (updatePolicy){
+ case IMMEDEATE:
+ case IMMEDIATE:
+ commit();
+ break;
+ case EXPLCIT:
+ case EXPLICIT:
+ this.newValue = new Object[]{newLocalValue};
+ break;
+ case LOG_ONLY:
+ informListeners(this.value, newLocalValue);
+ this.newValue = null;
+ break;
+ case NEVER:
+ this.newValue = null;
+ break;
+ default:
+ this.newValue = null;
+ break;
+ }
+ }
+ }
+ return value;
+ }
+
+ /**
+ * Method to check for and apply a new value. Depending on the {@link UpdatePolicy}
+ * the value is immediately or deferred visible (or it may even be ignored completely).
+ *
+ * @return true, if a new value has been detected. The value may not be visible depending on the current
+ * {@link UpdatePolicy} in place.
+ */
+ public boolean updateValue() {
+ if(this.value==null && this.newValue==null){
+ this.value = evaluateValue();
+ return false;
+ }
+ T newValue = evaluateValue();
+ if (Objects.equals(newValue, this.value)) {
+ return false;
+ }
+ switch (this.updatePolicy) {
+ case LOG_ONLY:
+ Logger.getLogger(getClass().getName()).info("Discard change on " + this + ", newValue=" + newValue);
+ informListeners(value, newValue);
+ this.newValue = null;
+ break;
+ case NEVER:
+ this.newValue = null;
+ break;
+ case EXPLCIT:
+ case IMMEDEATE:
+ default:
+ this.newValue = new Object[]{newValue};
+ commit();
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * Evaluates the current value dynamically from the underlying configuration.
+ *
+ * @return the current actual value, or null.
+ */
+ public T evaluateValue() {
+ T value = null;
+
+ for (String key : keys) {
+ if (propertyConverter == null) {
+ value = configuration.get(key, targetType);
+ } else {
+ String source = configuration.get(key);
+ ConversionContext ctx = new ConversionContext.Builder(configuration,
+ configuration.getContext(), key, targetType).build();
+ value = propertyConverter.convert(source, ctx);
+ }
+
+ if (value != null) {
+ break;
+ }
+ }
+
+ return value;
+ }
+
+ /**
+ * Access a new value that has not yet been committed.
+ *
+ * @return the uncommitted new value, or null.
+ */
+ public T getNewValue() {
+ T nv = newValue==null?null:(T)newValue[0];
+ if (nv != null) {
+ return nv;
+ }
+ return null;
+ }
+
+
+ /**
+ * Serialization implementation that strips away the non serializable Optional part.
+ *
+ * @param oos the output stream
+ * @throws IOException if serialization fails.
+ */
+ private void writeObject(ObjectOutputStream oos) throws IOException {
+ oos.writeObject(getUpdatePolicy());
+ oos.writeObject(get());
+ }
+
+ /**
+ * Reads an instance from the input stream.
+ *
+ * @param ois the object input stream
+ * @throws IOException if deserialization fails.
+ * @throws ClassNotFoundException
+ */
+ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
+ this.updatePolicy = (UpdatePolicy) ois.readObject();
+ if (isPresent()) {
+ this.value = (T) ois.readObject();
+ }
+ newValue = null;
+ }
+
+
+ /**
+ * Simple helper that allows keeping the listeners registered as weak references, hereby avoiding any
+ * memory leaks.
+ *
+ * @param <I> the type
+ */
+ private class WeakList<I> {
+ final List<WeakReference<I>> refs = new LinkedList<>();
+
+ /**
+ * Adds a new instance.
+ *
+ * @param t the new instance, not null.
+ */
+ void add(I t) {
+ refs.add(new WeakReference<>(t));
+ }
+
+ /**
+ * Removes a instance.
+ *
+ * @param t the instance to be removed.
+ */
+ void remove(I t) {
+ synchronized (refs) {
+ for (Iterator<WeakReference<I>> iterator = refs.iterator(); iterator.hasNext(); ) {
+ WeakReference<I> ref = iterator.next();
+ I instance = ref.get();
+ if (instance == null || instance == t) {
+ iterator.remove();
+ break;
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Access a list (copy) of the current instances that were not discarded by the GC.
+ *
+ * @return the list of accessible items.
+ */
+ public List<I> get() {
+ synchronized (refs) {
+ List<I> res = new ArrayList<>();
+ for (Iterator<WeakReference<I>> iterator = refs.iterator(); iterator.hasNext(); ) {
+ WeakReference<I> ref = iterator.next();
+ I instance = ref.get();
+ if (instance == null) {
+ iterator.remove();
+ } else {
+ res.add(instance);
+ }
+ }
+ return res;
+ }
+ }
+ }
+
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/346a4f38/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/ServiceLoaderServiceContext.java
----------------------------------------------------------------------
diff --git a/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/ServiceLoaderServiceContext.java b/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/ServiceLoaderServiceContext.java
new file mode 100644
index 0000000..3c04415
--- /dev/null
+++ b/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/ServiceLoaderServiceContext.java
@@ -0,0 +1,190 @@
+/*
+ * 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.cdi;
+
+import org.apache.tamaya.ConfigException;
+import org.apache.tamaya.spi.ServiceContext;
+import org.apache.tamaya.spisupport.PriorityServiceComparator;
+
+import javax.annotation.Priority;
+import java.io.IOException;
+import java.net.URL;
+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 {
+ private static final Logger LOG = Logger.getLogger(ServiceLoaderServiceContext.class.getName());
+ /**
+ * List current services loaded, per class.
+ */
+ private final ConcurrentHashMap<Class<?>, List<Object>> servicesLoaded = new ConcurrentHashMap<>();
+ /**
+ * Singletons.
+ */
+ private final Map<Class<?>, Object> singletons = new ConcurrentHashMap<>();
+ private Map<Class, Class> factoryTypes = new ConcurrentHashMap<>();
+
+ @Override
+ public <T> T getService(Class<T> serviceType) {
+ Object cached = singletons.get(serviceType);
+ if (cached == null) {
+ cached = create(serviceType);
+ if(cached!=null) {
+ singletons.put(serviceType, cached);
+ }
+ }
+ return serviceType.cast(cached);
+ }
+
+ @Override
+ public <T> T create(Class<T> serviceType) {
+ Class<? extends T> implType = factoryTypes.get(serviceType);
+ if(implType==null) {
+ Collection<T> services = getServices(serviceType);
+ if (services.isEmpty()) {
+ return null;
+ } else {
+ return getServiceWithHighestPriority(services, serviceType);
+ }
+ }
+ try {
+ return implType.newInstance();
+ } catch (Exception e) {
+ LOG.log(Level.SEVERE, "Failed to create instabce of " + implType.getName(), e);
+ return null;
+ }
+ }
+
+ /**
+ * 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);
+ }
+ Collections.sort(services, PriorityServiceComparator.getInstance());
+ services = Collections.unmodifiableList(services);
+ } catch (ServiceConfigurationError e) {
+ LOG.log(Level.WARNING,
+ "Error loading services current type " + serviceType, e);
+ if(services==null){
+ services = Collections.emptyList();
+ }
+ }
+ 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) {
+ T highestService = null;
+ // we do not need the priority stuff if the list contains only one element
+ if (services.size() == 1) {
+ highestService = services.iterator().next();
+ this.factoryTypes.put(serviceType, highestService.getClass());
+ return highestService;
+ }
+
+ Integer highestPriority = null;
+ int highestPriorityServiceCount = 0;
+
+ 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));
+ }
+ this.factoryTypes.put(serviceType, highestService.getClass());
+ return highestService;
+ }
+
+ @Override
+ public int ordinal() {
+ return 1;
+ }
+
+ @Override
+ public Enumeration<URL> getResources(String resource, ClassLoader cl) throws IOException{
+ if(cl==null){
+ cl = Thread.currentThread().getContextClassLoader();
+ }
+ return cl.getResources(resource);
+ }
+
+ @Override
+ public URL getResource(String resource, ClassLoader cl){
+ if(cl==null){
+ cl = Thread.currentThread().getContextClassLoader();
+ }
+ return cl.getResource(resource);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/346a4f38/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/TamayaCDIAccessor.java
----------------------------------------------------------------------
diff --git a/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/TamayaCDIAccessor.java b/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/TamayaCDIAccessor.java
new file mode 100644
index 0000000..1d7533c
--- /dev/null
+++ b/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/TamayaCDIAccessor.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.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 TamayaCDIAccessor 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){
+ TamayaCDIAccessor.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/346a4f38/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/TamayaCDIInjectionExtension.java
----------------------------------------------------------------------
diff --git a/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/TamayaCDIInjectionExtension.java b/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/TamayaCDIInjectionExtension.java
new file mode 100644
index 0000000..749c9b0
--- /dev/null
+++ b/modules/injection/cdi/src/main/java/org/apache/tamaya/cdi/TamayaCDIInjectionExtension.java
@@ -0,0 +1,277 @@
+/*
+ * 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.cdi;
+
+import org.apache.tamaya.ConfigException;
+import org.apache.tamaya.ConfigOperator;
+import org.apache.tamaya.Configuration;
+import org.apache.tamaya.ConfigurationProvider;
+import org.apache.tamaya.inject.api.Config;
+import org.apache.tamaya.inject.api.ConfigDefaultSections;
+import org.apache.tamaya.inject.api.WithConfigOperator;
+import org.apache.tamaya.inject.api.WithPropertyConverter;
+import org.apache.tamaya.spi.PropertyConverter;
+
+import javax.enterprise.context.spi.CreationalContext;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.spi.*;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Logger;
+
+
+/**
+ * CDI Extension module that adds injection mechanism for configuration.
+ *
+ * @see Config
+ * @see ConfigDefaultSections
+ * @see ConfigException
+ */
+public class TamayaCDIInjectionExtension implements Extension {
+
+ private static final Logger LOG = Logger.getLogger(TamayaCDIInjectionExtension.class.getName());
+
+ static final Map<Class, ConfigOperator> CUSTOM_OPERATORS = new ConcurrentHashMap<>();
+ static final Map<Class, PropertyConverter> CUSTOM_CONVERTERS = new ConcurrentHashMap<>();
+
+ private final Set<Type> types = new HashSet<>();
+ private Bean<?> convBean;
+
+ /**
+ * Constructor for loading logging its load.
+ */
+ public TamayaCDIInjectionExtension(){
+ LOG.finest("Loading Tamaya CDI Support...");
+ }
+
+ /**
+ * Method that checks the configuration injection points during deployment for available configuration.
+ * @param pb the bean to process.
+ * @param beanManager the bean manager to notify about new injections.
+ */
+ public void retrieveTypes(@Observes final ProcessBean<?> pb, BeanManager beanManager) {
+
+ final Set<InjectionPoint> ips = pb.getBean().getInjectionPoints();
+ CDIConfiguredType configuredType = new CDIConfiguredType(pb.getBean().getBeanClass());
+
+ boolean configured = false;
+ boolean logged = false;
+ for (InjectionPoint injectionPoint : ips) {
+ if (injectionPoint.getAnnotated().isAnnotationPresent(Config.class)) {
+ final Config annotation = injectionPoint.getAnnotated().getAnnotation(Config.class);
+ final ConfigDefaultSections typeAnnot = injectionPoint.getAnnotated().getAnnotation(ConfigDefaultSections.class);
+ final List<String> keys = evaluateKeys(injectionPoint.getMember().getName(),
+ annotation!=null?annotation.value():null,
+ typeAnnot!=null?typeAnnot.value():null);
+
+ final WithConfigOperator withOperatorAnnot = injectionPoint.getAnnotated().getAnnotation(WithConfigOperator.class);
+ if(withOperatorAnnot!=null){
+ tryLoadOpererator(withOperatorAnnot.value());
+ }
+ final WithPropertyConverter withConverterAnnot = injectionPoint.getAnnotated().getAnnotation(WithPropertyConverter.class);
+ if(withConverterAnnot!=null){
+ tryLoadConverter(withConverterAnnot.value());
+ }
+
+ // We don't want to wait until the injection really fails at runtime.
+ // If there is a non resolvable configuration, we want to know at startup.
+ Configuration config = ConfigurationProvider.getConfiguration();
+ String value = null;
+ for(String key:keys) {
+ value = config.get(key);
+ if(value!=null){
+ break;
+ }
+ }
+ if(value==null && !annotation.defaultValue().isEmpty()){
+ value = annotation.defaultValue();
+ }
+ if(value==null){
+ throw new ConfigException(String.format(
+ "Cannot resolve any of the possible configuration keys: %s. Please provide one of the given keys " +
+ "with a value in your configuration sources.",
+ keys.toString()));
+ }
+ types.add(injectionPoint.getType());
+ if(annotation!=null){
+ configured = true;
+ if(!logged) {
+ LOG.finest("Enabling Tamaya CDI Configuration on bean: " + configuredType.getName());
+ }
+ configuredType.addConfiguredMember(injectionPoint, keys);
+ }
+ }
+ }
+ if(configured) {
+ beanManager.fireEvent(configuredType);
+ }
+ }
+
+
+ public void captureConvertBean(@Observes final ProcessProducerMethod<?, ?> ppm) {
+ if (ppm.getAnnotated().isAnnotationPresent(Config.class)) {
+ convBean = ppm.getBean();
+ }
+
+ }
+
+ public void addConverter(@Observes final AfterBeanDiscovery abd, final BeanManager bm) {
+ if(!types.isEmpty()) {
+ abd.addBean(new ConverterBean(convBean, types));
+ }
+ }
+
+ private void tryLoadOpererator(Class<? extends ConfigOperator> operatorClass) {
+ Objects.requireNonNull(operatorClass);
+ if(ConfigOperator.class == operatorClass){
+ return;
+ }
+ try{
+ if(!CUSTOM_OPERATORS.containsKey(operatorClass)) {
+ CUSTOM_OPERATORS.put(operatorClass, operatorClass.newInstance());
+ }
+ } catch(Exception e){
+ throw new ConfigException("Custom ConfigOperator could not be loaded: " + operatorClass.getName(), e);
+ }
+ }
+
+ private void tryLoadConverter(Class<? extends PropertyConverter> converterClass) {
+ Objects.requireNonNull(converterClass);
+ if(PropertyConverter.class == converterClass){
+ return;
+ }
+ try{
+ if(!CUSTOM_CONVERTERS.containsKey(converterClass)) {
+ CUSTOM_CONVERTERS.put(converterClass, converterClass.newInstance());
+ }
+ } catch(Exception e){
+ throw new ConfigException("Custom PropertyConverter could not be loaded: " + converterClass.getName(), e);
+ }
+ }
+
+ /**
+ * Evaluates the effective keys to be used. if no {@code keys} are defined, {@code memberName} is used.
+ * The effective keys are then combined with the sections given (if any) and only, if the given keys are not
+ * absolute keys (surrounded by brackets).
+ * @param memberName the default member name, not null.
+ * @param keys the keys, may be empty, or null.
+ * @param sections the default sections, may be empty. May also be null.
+ * @return the list of keys to be finally used for configuration resolution in order of
+ * precedence. The first keys in the list that could be successfully resolved define the final
+ * configuration value.
+ */
+ public static List<String> evaluateKeys(String memberName, String[] keys, String[] sections) {
+ List<String> effKeys = new ArrayList<>();
+ if(keys!=null){
+ effKeys.addAll(Arrays.asList(keys));
+ }
+ if (effKeys.isEmpty()) {
+ effKeys.add(memberName);
+ }
+ ListIterator<String> iterator = effKeys.listIterator();
+ while (iterator.hasNext()) {
+ String next = iterator.next();
+ if (next.startsWith("[") && next.endsWith("]")) {
+ // absolute key, strip away brackets, take key as is
+ iterator.set(next.substring(1, next.length() - 1));
+ } else {
+ if (sections != null && sections.length>0) {
+ // Remove original entry, since it will be replaced with prefixed entries
+ iterator.remove();
+ // Add prefixed entries, including absolute (root) entry for "" area keys.
+ for (String area : sections) {
+ iterator.add(area.isEmpty() ? next : area + '.' + next);
+ }
+ }
+ }
+ }
+ return effKeys;
+ }
+
+
+ /**
+ * Internally used conversion bean.
+ */
+ private static class ConverterBean implements Bean<Object> {
+
+ private final Bean<Object> delegate;
+ private final Set<Type> types;
+
+ public ConverterBean(final Bean convBean, final Set<Type> types) {
+ this.types = types;
+ this.delegate = convBean;
+ }
+
+ @Override
+ public Set<Type> getTypes() {
+ return types;
+ }
+
+ @Override
+ public Class<?> getBeanClass() {
+ return delegate.getBeanClass();
+ }
+
+ @Override
+ public Set<InjectionPoint> getInjectionPoints() {
+ return delegate.getInjectionPoints();
+ }
+
+ @Override
+ public String getName() {
+ return delegate.getName();
+ }
+
+ @Override
+ public Set<Annotation> getQualifiers() {
+ return delegate.getQualifiers();
+ }
+
+ @Override
+ public Class<? extends Annotation> getScope() {
+ return delegate.getScope();
+ }
+
+ @Override
+ public Set<Class<? extends Annotation>> getStereotypes() {
+ return delegate.getStereotypes();
+ }
+
+ @Override
+ public boolean isAlternative() {
+ return delegate.isAlternative();
+ }
+
+ @Override
+ public boolean isNullable() {
+ return delegate.isNullable();
+ }
+
+ @Override
+ public Object create(CreationalContext<Object> creationalContext) {
+ return delegate.create(creationalContext);
+ }
+
+ @Override
+ public void destroy(Object instance, CreationalContext<Object> creationalContext) {
+ delegate.destroy(instance, creationalContext);
+ }
+ }
+
+}