http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/src/test/java/org/apache/tamaya/inject/internal/DefaultDynamicValueTest.java ---------------------------------------------------------------------- diff --git a/modules/injection/src/test/java/org/apache/tamaya/inject/internal/DefaultDynamicValueTest.java b/modules/injection/src/test/java/org/apache/tamaya/inject/internal/DefaultDynamicValueTest.java deleted file mode 100644 index dd16f36..0000000 --- a/modules/injection/src/test/java/org/apache/tamaya/inject/internal/DefaultDynamicValueTest.java +++ /dev/null @@ -1,315 +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 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.inject.internal; - -import org.apache.tamaya.ConfigException; -import org.apache.tamaya.ConfigurationProvider; -import org.apache.tamaya.builder.ConfigurationBuilder; -import org.apache.tamaya.inject.api.ConfiguredItemSupplier; -import org.apache.tamaya.inject.api.DynamicValue; -import org.apache.tamaya.inject.api.Config; -import org.apache.tamaya.inject.api.UpdatePolicy; -import org.apache.tamaya.spi.ConversionContext; -import org.apache.tamaya.spi.PropertyConverter; -import org.apache.tamaya.spi.PropertySource; -import org.apache.tamaya.spi.PropertyValue; -import org.junit.Test; - -import org.apache.tamaya.Configuration; - -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.HashMap; -import java.util.Map; - -import static org.junit.Assert.*; - -/** - * Tests for {@link org.apache.tamaya.inject.internal.DefaultDynamicValue}. - */ -public class DefaultDynamicValueTest { - - @Config("a") - String myValue; - - @Config("a") - String myValue2; - - @Config("a") - void setterMethod(String value){ - - } - - private PropertyChangeEvent event; - - private PropertyChangeListener consumer = new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - event = evt; - } - }; - - private Map<String,String> properties = new HashMap<>(); - private Configuration config = new ConfigurationBuilder().addPropertySources( - new PropertySource() { - @Override - public int getOrdinal() { - return 0; - } - - @Override - public String getName() { - return "test"; - } - - @Override - public PropertyValue get(String key) { - return PropertyValue.of(key,properties.get(key),getName()); - } - - @Override - public Map<String, String> getProperties() { - return properties; - } - - @Override - public boolean isScannable() { - return false; - } - } - ).build(); - - @Test - public void testOf_Field() throws Exception { - DynamicValue val = DefaultDynamicValue.of(getClass().getDeclaredField("myValue"), - ConfigurationProvider.getConfiguration()); - assertNotNull(val); - } - - @Test - public void testOf_Method() throws Exception { - DynamicValue val = DefaultDynamicValue.of(getClass().getDeclaredMethod("setterMethod", String.class), - config); - assertNotNull(val); - } - - @Test - public void testCommitAndGet() throws Exception { - properties.put("a","aValue"); - DynamicValue val = DefaultDynamicValue.of(getClass().getDeclaredField("myValue"), - config); - assertNotNull(val); - assertEquals("aValue",val.evaluateValue()); - } - - @Test - public void testCommitAndGets() throws Exception { - properties.put("a","aValue"); - DynamicValue val = DefaultDynamicValue.of(getClass().getDeclaredField("myValue"), - config); - val.setUpdatePolicy(UpdatePolicy.EXPLCIT); - assertNotNull(val); - assertEquals("aValue",val.evaluateValue()); - // change config - val.get(); - this.properties.put("a", "aValue2"); - assertTrue(val.updateValue()); - assertEquals("aValue2", val.commitAndGet()); - } - - @Test - public void testCommit() throws Exception { - properties.put("a", "aValue"); - DynamicValue val = DefaultDynamicValue.of(getClass().getDeclaredField("myValue"), - config); - val.setUpdatePolicy(UpdatePolicy.EXPLCIT); - assertNotNull(val); - assertEquals("aValue", val.evaluateValue()); - // change config - val.get(); - this.properties.put("a", "aValue2"); - assertEquals("aValue2", val.evaluateValue()); - assertTrue(val.updateValue()); - val.commit(); - assertEquals("aValue2", val.get()); - } - - @Test - public void testGetSetUpdatePolicy() throws Exception { - DynamicValue val = DefaultDynamicValue.of(getClass().getDeclaredField("myValue"), - config); - for(UpdatePolicy pol: UpdatePolicy.values()) { - val.setUpdatePolicy(pol); - assertEquals(pol, val.getUpdatePolicy()); - } - } - - @Test - public void testAddRemoveListener() throws Exception { - properties.put("a","aValue"); - DynamicValue val = DefaultDynamicValue.of(getClass().getDeclaredField("myValue"), - config); - val.setUpdatePolicy(UpdatePolicy.IMMEDEATE); - val.addListener(consumer); - // change config - val.get(); - this.properties.put("a", "aValue2"); - val.get(); - assertNotNull(event); - event = null; - val.removeListener(consumer); - this.properties.put("a", "aValue3"); - val.updateValue(); - assertNull(event); - } - - @Test - public void testGet() throws Exception { - properties.put("a", "aValue"); - DynamicValue val = DefaultDynamicValue.of(getClass().getDeclaredField("myValue"), - config); - val.setUpdatePolicy(UpdatePolicy.IMMEDEATE); - properties.put("a", "aValue2"); - val.updateValue(); - assertEquals("aValue2", val.get()); - } - - @Test - public void testUpdateValue() throws Exception { - properties.put("a","aValue"); - DynamicValue val = DefaultDynamicValue.of(getClass().getDeclaredField("myValue"), - config); - val.setUpdatePolicy(UpdatePolicy.EXPLCIT); - assertNotNull(val.get()); - assertEquals("aValue", val.get()); - val.updateValue(); - assertEquals("aValue", val.get()); - val.setUpdatePolicy(UpdatePolicy.IMMEDEATE); - val.updateValue(); - assertEquals("aValue",val.get()); - } - - @Test - public void testEvaluateValue() throws Exception { - properties.put("a","aValue"); - DynamicValue val = DefaultDynamicValue.of(getClass().getDeclaredField("myValue"), - config); - val.setUpdatePolicy(UpdatePolicy.EXPLCIT); - assertNotNull(val.get()); - assertEquals("aValue",val.evaluateValue()); - properties.put("a", "aValue2"); - assertEquals("aValue2", val.evaluateValue()); - } - - @Test - public void testGetNewValue() throws Exception { - properties.put("a","aValue"); - DynamicValue val = DefaultDynamicValue.of(getClass().getDeclaredField("myValue"), - config); - val.setUpdatePolicy(UpdatePolicy.EXPLCIT); - val.get(); - assertNull(val.getNewValue()); - properties.put("a", "aValue2"); - val.get(); - assertNotNull(val.getNewValue()); - assertEquals("aValue2", val.getNewValue()); - val.commit(); - assertNull(val.getNewValue()); - } - - @Test - public void testIsPresent() throws Exception { - - } - - @Test - public void testIfPresent() throws Exception { - properties.put("a","aValue"); - DynamicValue val = DefaultDynamicValue.of(getClass().getDeclaredField("myValue"), - config); - val.setUpdatePolicy(UpdatePolicy.IMMEDEATE); - assertTrue(val.isPresent()); - properties.remove("a"); - val.updateValue(); - assertFalse(val.isPresent()); - } - - @Test - public void testOrElse() throws Exception { - DynamicValue val = DefaultDynamicValue.of(getClass().getDeclaredField("myValue"), - config); - val.setUpdatePolicy(UpdatePolicy.IMMEDEATE); - assertEquals("bla", val.orElse("bla")); - properties.put("a","aValue"); - val.updateValue(); - assertEquals("aValue", val.orElse("bla")); - } - - @Test - public void testOrElseGet() throws Exception { - DynamicValue val = DefaultDynamicValue.of(getClass().getDeclaredField("myValue"), - config); - val.setUpdatePolicy(UpdatePolicy.IMMEDEATE); - assertEquals("bla", val.orElseGet(new ConfiguredItemSupplier() { - @Override - public Object get() { - return "bla"; - } - })); - properties.put("a", "aValue"); - val.updateValue(); - assertEquals("aValue", val.orElseGet(new ConfiguredItemSupplier() { - @Override - public Object get() { - return "bla"; - } - })); - } - - @Test(expected = ConfigException.class) - public void testOrElseThrow() throws Throwable { - DynamicValue val = DefaultDynamicValue.of(getClass().getDeclaredField("myValue"), - config); - val.setUpdatePolicy(UpdatePolicy.EXPLCIT); - val.get(); - properties.put("a", "aValue"); - assertEquals("aValue", val.orElseThrow(new ConfiguredItemSupplier() { - @Override - public ConfigException get() { - return new ConfigException("bla"); - } - })); - properties.remove("a"); - val.updateValue(); - assertEquals("aValue", val.orElseThrow(new ConfiguredItemSupplier() { - @Override - public ConfigException get() { - return new ConfigException("bla"); - } - })); - } - - private static final class DoublicatingConverter implements PropertyConverter<String>{ - - @Override - public String convert(String value, ConversionContext context) { - return value + value; - } - } -} \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/src/test/resources/META-INF/services/org.apache.tamaya.spi.PropertySource ---------------------------------------------------------------------- diff --git a/modules/injection/src/test/resources/META-INF/services/org.apache.tamaya.spi.PropertySource b/modules/injection/src/test/resources/META-INF/services/org.apache.tamaya.spi.PropertySource deleted file mode 100644 index 5dfb894..0000000 --- a/modules/injection/src/test/resources/META-INF/services/org.apache.tamaya.spi.PropertySource +++ /dev/null @@ -1,19 +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.inject.TestPropertySource \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/standalone/pom.xml ---------------------------------------------------------------------- diff --git a/modules/injection/standalone/pom.xml b/modules/injection/standalone/pom.xml new file mode 100644 index 0000000..5d5c975 --- /dev/null +++ b/modules/injection/standalone/pom.xml @@ -0,0 +1,104 @@ +<!-- + ~ 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. + --> +<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-injection</artifactId> + <name>Apache Tamaya odules - Injection Standalone</name> + <packaging>bundle</packaging> + + <properties> + <jdkVersion>1.7</jdkVersion> + </properties> + + <dependencies> + <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-api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.tamaya</groupId> + <artifactId>tamaya-core</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.tamaya.ext</groupId> + <artifactId>tamaya-resolver</artifactId> + <version>${project.version}</version> + <optional>true</optional> + </dependency> + <dependency> + <groupId>org.apache.tamaya.ext</groupId> + <artifactId>tamaya-builder</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.tamaya.ext</groupId> + <artifactId>tamaya-events</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>java-hamcrest</artifactId> + </dependency> + + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + <configuration> + <instructions> + <Export-Package> + org.apache.tamaya.inject + </Export-Package> + <Private-Package> + org.apache.tamaya.inject.internal + </Private-Package> + </instructions> + </configuration> + </plugin> + </plugins> + </build> + +</project> http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/ConfigurationInjection.java ---------------------------------------------------------------------- diff --git a/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/ConfigurationInjection.java b/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/ConfigurationInjection.java new file mode 100644 index 0000000..79d6218 --- /dev/null +++ b/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/ConfigurationInjection.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.inject; + +import org.apache.tamaya.spi.ServiceContextManager; + +/** + * Singleton accessor class for accessing {@link ConfigurationInjector} instances. + */ +public final class ConfigurationInjection { + + /** + * Singleton constructor. + */ + private ConfigurationInjection() { + } + + /** + * Get the current injector instance. + * + * @return the current injector, not null. + */ + public static ConfigurationInjector getConfigurationInjector() { + return ServiceContextManager.getServiceContext().getService(ConfigurationInjector.class); + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/ConfigurationInjector.java ---------------------------------------------------------------------- diff --git a/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/ConfigurationInjector.java b/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/ConfigurationInjector.java new file mode 100644 index 0000000..563ae47 --- /dev/null +++ b/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/ConfigurationInjector.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.inject; + + +import org.apache.tamaya.Configuration; +import org.apache.tamaya.inject.api.ConfiguredItemSupplier; + +/** + * Accessor interface for injection of configuration and configuration templates. + */ +public interface ConfigurationInjector { + + /** + * Configures the current instance and registers necessary listener to forward config change events as + * defined by the current annotations in place. + * + * Unannotated types are ignored. + * + * @param <T> the type of the instance. + * @param instance the instance to be configured + * @return the configured instance (allows chaining of operations). + */ + <T> T configure(T instance); + + /** + * Configures the current instance and registers necessary listener to forward config change events as + * defined by the current annotations in place. + * + * Unannotated types are ignored. + * + * @param <T> the type of the instance. + * @param instance the instance to be configured + * @param config the configuration to be used for injection. + * @return the configured instance (allows chaining of operations). + */ + <T> T configure(T instance, Configuration config); + + /** + * Creates a template implementing the annotated methods based on current configuration data. + * + * @param <T> the type of the template. + * @param templateType the type of the template to be created. + * @return the configured template. + */ + <T> T createTemplate(Class<T> templateType); + + /** + * Creates a template implementting the annotated methods based on current configuration data. + * + * @param <T> the type of the template. + * @param config the configuration to be used for backing the template. + * @param templateType the type of the template to be created. + * @return the configured template. + */ + <T> T createTemplate(Class<T> templateType, Configuration config); + + + /** + * Creates a supplier for configured instances of the given type {@code T}. + * + * @param supplier the supplier to create new instances. + * @param <T> the target type. + * @return a supplier creating configured instances of {@code T}. + */ + <T> ConfiguredItemSupplier<T> getConfiguredSupplier(ConfiguredItemSupplier<T> supplier); + + /** + * Creates a supplier for configured instances of the given type {@code T}. + * + * @param supplier the supplier to create new instances. + * @param config the configuration to be used for backing the supplier. + * @param <T> the target type. + * @return a supplier creating configured instances of {@code T}. + */ + <T> ConfiguredItemSupplier<T> getConfiguredSupplier(ConfiguredItemSupplier<T> supplier, Configuration config); + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/ConfigTemplateInvocationHandler.java ---------------------------------------------------------------------- diff --git a/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/ConfigTemplateInvocationHandler.java b/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/ConfigTemplateInvocationHandler.java new file mode 100644 index 0000000..5d634e1 --- /dev/null +++ b/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/ConfigTemplateInvocationHandler.java @@ -0,0 +1,73 @@ +/* + * 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.inject.internal; + +import org.apache.tamaya.Configuration; +import org.apache.tamaya.ConfigurationProvider; +import org.apache.tamaya.TypeLiteral; +import org.apache.tamaya.inject.api.DynamicValue; +import org.apache.tamaya.inject.spi.ConfiguredType; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.Objects; + +/** + * Invocation handler that handles request against a configuration template. + */ +public final class ConfigTemplateInvocationHandler implements InvocationHandler { + + /** + * The configured type. + */ + private final ConfiguredType type; + + /** + * Creates a new handler instance. + * + * @param type the target type, not null. + */ + public ConfigTemplateInvocationHandler(Class<?> type) { + this.type = new ConfiguredTypeImpl(Objects.requireNonNull(type)); + if (!type.isInterface()) { + throw new IllegalArgumentException("Can only proxy interfaces as configuration templates."); + } + InjectionHelper.sendConfigurationEvent(this.type); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + Configuration config = ConfigurationProvider.getConfiguration(); + if ("toString".equals(method.getName())) { + return "Configured Proxy -> " + this.type.getType().getName(); + } else if ("hashCode".equals(method.getName())) { + return Objects.hashCode(proxy); + } else if ("equals".equals(method.getName())) { + return Objects.equals(proxy, args[0]); + } else if ("get".equals(method.getName())) { + return config; + } + if (method.getReturnType() == DynamicValue.class) { + return DefaultDynamicValue.of(method, config); + } + String[] retKey = new String[1]; + String configValue = InjectionHelper.getConfigValue(method, retKey, config); + return InjectionHelper.adaptValue(method, TypeLiteral.of(method.getReturnType()), retKey[0], configValue); + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/ConfiguredFieldImpl.java ---------------------------------------------------------------------- diff --git a/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/ConfiguredFieldImpl.java b/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/ConfiguredFieldImpl.java new file mode 100644 index 0000000..64b0c95 --- /dev/null +++ b/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/ConfiguredFieldImpl.java @@ -0,0 +1,170 @@ +/* + * 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.inject.internal; + +import org.apache.tamaya.ConfigException; +import org.apache.tamaya.Configuration; +import org.apache.tamaya.ConfigurationProvider; +import org.apache.tamaya.TypeLiteral; +import org.apache.tamaya.inject.api.DynamicValue; +import org.apache.tamaya.inject.api.InjectionUtils; +import org.apache.tamaya.inject.spi.ConfiguredField; + +import java.lang.reflect.Field; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; +import java.util.Collection; +import java.util.Objects; + +/** + * Small class that contains and manages all information anc access to a configured field and a concrete instance current + * it (referenced by a weak reference). It also implements all aspects current keys filtering, converting any applying the + * final keys by reflection. + */ +public class ConfiguredFieldImpl implements ConfiguredField{ + /** + * The configured field instance. + */ + protected final Field annotatedField; + + /** + * Models a configured field and provides mechanisms for injection. + * + * @param field the field instance. + */ + public ConfiguredFieldImpl(Field field) { + Objects.requireNonNull(field); + this.annotatedField = field; + } + + + /** + * Evaluate the initial keys fromMap the configuration and applyChanges it to the field. + * + * @param target the target instance. + * @throws ConfigException if evaluation or conversion failed. + */ + public void configure(Object target, Configuration config) throws ConfigException { + if (this.annotatedField.getType() == DynamicValue.class) { + applyDynamicValue(target); + } else { + applyValue(target, config, false); + } + } + + + /** + * This method instantiates and assigns a dynamic value. + * + * @param target the target instance, not null. + * @throws ConfigException if the configuration required could not be resolved or converted. + */ + private void applyDynamicValue(Object target) throws ConfigException { + Objects.requireNonNull(target); + try { + AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { + @Override + public Object run() throws Exception { + annotatedField.setAccessible(true); + return annotatedField; + } + }); + annotatedField.set(target, + DefaultDynamicValue.of(annotatedField, ConfigurationProvider.getConfiguration())); + } catch (Exception e) { + throw new ConfigException("Failed to annotation configured field: " + this.annotatedField.getDeclaringClass() + .getName() + '.' + annotatedField.getName(), e); + } + } + + /** + * This method applies a configuration to the field. + * + * @param target the target instance, not null. + * @param config The configuration to be used. + * @param resolve set to true, if expression resolution should be applied on the keys passed. + * @throws ConfigException if the configuration required could not be resolved or converted. + */ + private void applyValue(Object target, Configuration config, boolean resolve) throws ConfigException { + Objects.requireNonNull(target); + try { + String[] retKey = new String[1]; + String configValue = InjectionHelper.getConfigValue(this.annotatedField, retKey, config); + // Next step perform expression resolution, if any + String evaluatedValue = resolve && configValue != null + ? InjectionHelper.evaluateValue(configValue) + : configValue; + + // Check for adapter/filter + Object value = InjectionHelper.adaptValue(this.annotatedField, + TypeLiteral.of(this.annotatedField.getType()), retKey[0], evaluatedValue); + AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { + @Override + public Object run() throws Exception { + annotatedField.setAccessible(true); + return annotatedField; + } + }); + if(value!=null) { + annotatedField.set(target, value); + } + } catch (Exception e) { + throw new ConfigException("Failed to evaluate annotated field: " + this.annotatedField.getDeclaringClass() + .getName() + '.' + annotatedField.getName(), e); + } + } + + /** + * Get the field's type. + * @return the field's type, not null. + */ + @Override + public Class<?> getType(){ + return this.annotatedField.getType(); + } + + /** + * Access the applyable configuration keys for this field. + * @return the configuration keys, never null. + */ + @Override + public Collection<String> getConfiguredKeys(){ + return InjectionUtils.getKeys(this.annotatedField); + } + + @Override + public String toString() { + return "ConfiguredField[" + getSignature() + ']'; + } + + @Override + public String getName() { + return annotatedField.getName(); + } + + @Override + public String getSignature() { + return getName()+':'+getType().getName(); + } + + @Override + public Field getAnnotatedField() { + return annotatedField; + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/ConfiguredSetterMethod.java ---------------------------------------------------------------------- diff --git a/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/ConfiguredSetterMethod.java b/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/ConfiguredSetterMethod.java new file mode 100644 index 0000000..b69df20 --- /dev/null +++ b/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/ConfiguredSetterMethod.java @@ -0,0 +1,139 @@ +/* + * 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.inject.internal; + +import org.apache.tamaya.ConfigException; +import org.apache.tamaya.Configuration; +import org.apache.tamaya.TypeLiteral; +import org.apache.tamaya.inject.api.InjectionUtils; +import org.apache.tamaya.inject.spi.ConfiguredMethod; + +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; +import java.util.Collection; +import java.util.Objects; + +/** + * Small class that contains and manages all information and access to a configured field and a concrete instance current + * it (referenced by a weak reference). It also implements all aspects current keys filtering, conversions any applying the + * final keys by reflection. + */ +public class ConfiguredSetterMethod implements ConfiguredMethod { + + /** + * The configured field instance. + */ + private Method setterMethod; + private Collection<String> configuredKeys; + + /** + * Models a configured field and provides mechanisms for injection. + * + * @param method the method instance. + */ + public ConfiguredSetterMethod(Method method) { + if (void.class.equals(method.getReturnType()) && + method.getParameterTypes().length == 1) { + this.setterMethod = method; + } + } + + @Override + public void configure(Object target, Configuration config) throws ConfigException { + String[] retKey = new String[1]; + String configValue = InjectionHelper.getConfigValue(this.setterMethod, retKey, config); + Objects.requireNonNull(target); + try { + String evaluatedString = configValue != null + ? InjectionHelper.evaluateValue(configValue) + : configValue; + + // Check for adapter/filter + Object value = InjectionHelper.adaptValue( + this.setterMethod, TypeLiteral.of(this.setterMethod.getParameterTypes()[0]), + retKey[0], evaluatedString); + + AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { + @Override + public Object run() throws Exception { + setterMethod.setAccessible(true); + return setterMethod; + } + }); + + setterMethod.invoke(target, value); + } catch (Exception e) { + throw new ConfigException("Failed to annotation configured method: " + this.setterMethod.getDeclaringClass() + .getName() + '.' + setterMethod.getName(), e); + } + } + + + /** + * Access the applyable configuration keys for this field. + * + * @return the configuration keys, never null. + */ + @Override + public Collection<String> getConfiguredKeys() { + return InjectionUtils.getKeys(this.setterMethod); + } + + /** + * Get the type to be set on the setter method. + * @return the setter type. + */ + @Override + public Class<?>[] getParameterTypes() { + return this.setterMethod.getParameterTypes(); + } + + /** + * Access the annotated method. + * @return the annotated method, not null. + */ + @Override + public Method getAnnotatedMethod() { + return this.setterMethod; + } + + @Override + public String getName() { + return this.setterMethod.getName(); + } + + @Override + public String getSignature() { + return "void " + this.setterMethod.getName()+'('+ printTypes(getParameterTypes())+')'; + } + + private String printTypes(Class<?>[] parameterTypes) { + StringBuilder b = new StringBuilder(); + for(Class cl:parameterTypes){ + b.append(cl.getName()); + b.append(','); + } + if(b.length()>0){ + b.setLength(b.length()-1); + } + return b.toString(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/ConfiguredTypeImpl.java ---------------------------------------------------------------------- diff --git a/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/ConfiguredTypeImpl.java b/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/ConfiguredTypeImpl.java new file mode 100644 index 0000000..b40f6c9 --- /dev/null +++ b/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/ConfiguredTypeImpl.java @@ -0,0 +1,237 @@ +/* + * 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.inject.internal; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.logging.Logger; + +import org.apache.tamaya.ConfigException; +import org.apache.tamaya.Configuration; +import org.apache.tamaya.ConfigurationProvider; +import org.apache.tamaya.inject.api.ConfigAutoInject; +import org.apache.tamaya.inject.api.NoConfig; +import org.apache.tamaya.inject.api.Config; +import org.apache.tamaya.inject.api.ConfigDefaultSections; +import org.apache.tamaya.inject.spi.ConfiguredField; +import org.apache.tamaya.inject.spi.ConfiguredMethod; +import org.apache.tamaya.inject.spi.ConfiguredType; + +/** + * Structure that contains and manages configuration related things for a configured type registered. + * Created by Anatole on 03.10.2014. + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public class ConfiguredTypeImpl implements ConfiguredType{ + /** The log used. */ + private static final Logger LOG = Logger.getLogger(ConfiguredTypeImpl.class.getName()); + /** + * A list with all annotated instance variables. + */ + private final List<ConfiguredField> configuredFields = new ArrayList<>(); + /** + * A list with all annotated methods (templates). + */ + private final List<ConfiguredMethod> configuredSetterMethods = new ArrayList<>(); + /** + * The basic type. + */ + private final Class type; + + /** + * Creates an instance of this class hereby evaluating the config annotations given for later effective + * injection (configuration) of instances. + * + * @param type the instance type. + */ + public ConfiguredTypeImpl(Class type) { + this.type = Objects.requireNonNull(type); + if(!isConfigured(type)){ + LOG.info("Auto-Configuring type: " + type.getName()); + initFields(type, true); + initMethods(type, true); + }else { + ConfigAutoInject autoInject = (ConfigAutoInject) type.getAnnotation(ConfigAutoInject.class); + if (autoInject != null) { + initFields(type, autoInject != null); + initMethods(type, autoInject != null); + } else { + initFields(type, false); + initMethods(type, false); + } + } + } + + private void initFields(Class type, boolean autoConfigure) { + for (Field f : type.getDeclaredFields()) { + if (f.isAnnotationPresent(NoConfig.class)) { + LOG.finest("Ignored @NoConfig annotated field " + f.getClass().getName() + "#" + + f.toGenericString()); + continue; + } + if (Modifier.isFinal(f.getModifiers())) { + LOG.finest("Ignored final field " + f.getClass().getName() + "#" + + f.toGenericString()); + continue; + } + if (f.isSynthetic()) { + LOG.finest("Ignored synthetic field " + f.getClass().getName() + "#" + + f.toGenericString()); + continue; + } + try { + if(isConfiguredField(f) || autoConfigure) { + ConfiguredField configuredField = new ConfiguredFieldImpl(f); + configuredFields.add(configuredField); + LOG.finer("Registered field " + f.getClass().getName() + "#" + + f.toGenericString()); + } + } catch (Exception e) { + throw new ConfigException("Failed to initialized configured field: " + + f.getDeclaringClass().getName() + '.' + f.getName(), e); + } + } + } + + private void initMethods(Class type, boolean autoConfigure) { + // TODO revisit this logic here... + for (Method m : type.getDeclaredMethods()) { + if (m.isAnnotationPresent(NoConfig.class)) { + LOG.finest("Ignored @NoConfig annotated method " + m.getClass().getName() + "#" + + m.toGenericString()); + continue; + } + if (m.isSynthetic()) { + LOG.finest("Ignored synthetic method " + m.getClass().getName() + "#" + + m.toGenericString()); + continue; + } + if(isConfiguredMethod(m) || autoConfigure) { + Config propAnnot = m.getAnnotation(Config.class); + if (addPropertySetter(m, propAnnot)) { + LOG.finer("Added configured setter: " + m.getClass().getName() + "#" + + m.toGenericString()); + } + } + } + } + + private boolean addPropertySetter(Method m, Config prop) { + if (prop!=null) { + if (m.getParameterTypes().length == 1) { + // getter method + Class<?> returnType = m.getReturnType(); + if (void.class.equals(returnType)) { + try { + configuredSetterMethods.add(new ConfiguredSetterMethod(m)); + return true; + } catch (Exception e) { + throw new ConfigException("Failed to initialized configured setter method: " + + m.getDeclaringClass().getName() + '.' + m.getName(), e); + } + } + } + } + return false; + } + + + /** + * Method called to configure an instance. + * + * @param instance The instance to be configured. + */ + public void configure(Object instance) { + configure(instance, ConfigurationProvider.getConfiguration()); + } + + @Override + public void configure(Object instance, Configuration config) { + for (ConfiguredField field : configuredFields) { + field.configure(instance, config); + } + for (ConfiguredMethod method : configuredSetterMethods) { + method.configure(instance, config); +// // TODO, if method should be recalled on changes, corresponding callbacks could be registered here + } + } + + + public static 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 (isConfiguredField(field)) { + return true; + } + } + // if no class level annotation is there we might have method level annotations only + for (Method method : type.getDeclaredMethods()) { + if(isConfiguredMethod(method)) { + return true; + } + } + return false; + } + + public static boolean isConfiguredField(Field field) { + return field.isAnnotationPresent(Config.class); + } + + public static boolean isConfiguredMethod(Method method) { + return method.isAnnotationPresent(Config.class); + } + + @Override + public Class getType() { + return this.type; + } + + @Override + public String getName() { + return this.type.getName(); + } + + /** + * Get the registered configured fields. + * @return the registered configured fields, never null. + */ + @Override + public Collection<ConfiguredField> getConfiguredFields(){ + return configuredFields; + } + + /** + * Get the registered annotated setter methods. + * @return the registered annotated setter methods, never null. + */ + @Override + public Collection<ConfiguredMethod> getConfiguredMethods(){ + return configuredSetterMethods; + } + + @Override + public String toString() { + return "ConfigDefaultSections{"+ this.getType().getName() + '}'; + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/DefaultConfigurationInjector.java ---------------------------------------------------------------------- diff --git a/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/DefaultConfigurationInjector.java b/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/DefaultConfigurationInjector.java new file mode 100644 index 0000000..a3a7015 --- /dev/null +++ b/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/DefaultConfigurationInjector.java @@ -0,0 +1,179 @@ +/* + * 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.inject.internal; + +import org.apache.tamaya.Configuration; +import org.apache.tamaya.ConfigurationProvider; +import org.apache.tamaya.inject.ConfigurationInjector; + +import javax.annotation.Priority; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +import org.apache.tamaya.inject.api.ConfiguredItemSupplier; +import org.apache.tamaya.inject.api.NoConfig; +import org.apache.tamaya.inject.api.Config; +import org.apache.tamaya.inject.api.ConfigDefaultSections; +import org.apache.tamaya.inject.spi.ConfiguredType; + +/** + * Simple injector singleton that also registers instances configured using weak references. + */ +@Priority(0) +public final class DefaultConfigurationInjector implements ConfigurationInjector { + + private final Map<Class<?>, ConfiguredType> configuredTypes = new ConcurrentHashMap<>(); + + private static final Logger LOG = Logger.getLogger(DefaultConfigurationInjector.class.getName()); + + private boolean autoConfigureEnabled = true; + + /** + * Extract the configuration annotation config and registers it per class, for later reuse. + * + * @param type the type to be configured. + * @return the configured type registered. + */ + public ConfiguredType registerType(Class<?> type) { + ConfiguredType confType = configuredTypes.get(type); + if (confType == null) { + if(!isConfigAnnotated(type) && !autoConfigureEnabled){ + return null; + } + confType = new ConfiguredTypeImpl(type); + configuredTypes.put(type, confType); + InjectionHelper.sendConfigurationEvent(confType); + } + return confType; + } + + /** + * If set also non annotated instances can be configured or created as templates. + * @return true, if autoConfigureEnabled. + */ + public boolean isAutoConfigureEnabled(){ + return autoConfigureEnabled; + } + + /** + * Setting to true enables also configuration/templating of non annotated classes or + * interfaces. + * @param enabled true enables also configuration/templating of + */ + public void setAutoConfigureEnabled(boolean enabled){ + this.autoConfigureEnabled = enabled; + } + + /** + * CHecks if type is eligible for configuration injection. + * @param type the target type, not null. + * @return true, if the type, a method or field has Tamaya config annotation on it. + */ + private boolean isConfigAnnotated(Class<?> type) { + if(type.getClass().isAnnotationPresent(ConfigDefaultSections.class)){ + return true; + } + for (Field f : type.getDeclaredFields()) { + if (f.isAnnotationPresent(NoConfig.class) || f.isAnnotationPresent(Config.class)) { + return true; + } + } + for (Method m : type.getDeclaredMethods()) { + if (m.isAnnotationPresent(NoConfig.class) || m.isAnnotationPresent(Config.class)) { + return true; + } + } + return false; + } + + /** + * Configured the current instance and reigsterd necessary listener to forward config change events as + * defined by the current annotations in place. + * + * @param instance the instance to be configured + */ + @Override + public <T> T configure(T instance) { + return configure(instance, ConfigurationProvider.getConfiguration()); + } + + /** + * Configured the current instance and reigsterd necessary listener to forward config change events as + * defined by the current annotations in place. + * + * @param instance the instance to be configured + * @param config the target configuration, not null. + */ + @Override + public <T> T configure(T instance, Configuration config) { + Class<?> type = Objects.requireNonNull(instance).getClass(); + ConfiguredType configuredType = registerType(type); + if(configuredType!=null){ + configuredType.configure(instance, config); + }else{ + LOG.info("Instance passed is not configurable: " + instance); + } + return instance; + } + + /** + * Create a template implementting the annotated methods based on current configuration data. + * + * @param templateType the type of the template to be created. + */ + @Override + public <T> T createTemplate(Class<T> templateType) { + return createTemplate(templateType, ConfigurationProvider.getConfiguration()); + } + + /** + * Create a template implementting the annotated methods based on current configuration data. + * + * @param templateType the type of the template to be created. + * @param config the target configuration, not null. + */ + @Override + public <T> T createTemplate(Class<T> templateType, Configuration config) { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + if(cl==null){ + cl = this.getClass().getClassLoader(); + } + return templateType.cast(Proxy.newProxyInstance(cl, new Class[]{ConfiguredItemSupplier.class, Objects.requireNonNull(templateType)}, + new ConfigTemplateInvocationHandler(templateType))); + } + + @Override + public <T> ConfiguredItemSupplier<T> getConfiguredSupplier(final ConfiguredItemSupplier<T> supplier) { + return getConfiguredSupplier(supplier, ConfigurationProvider.getConfiguration()); + } + + @Override + public <T> ConfiguredItemSupplier<T> getConfiguredSupplier(final ConfiguredItemSupplier<T> supplier, final Configuration config) { + return new ConfiguredItemSupplier<T>() { + public T get() { + return configure(supplier.get(), config); + } + }; + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/fe7cd8f1/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/DefaultDynamicValue.java ---------------------------------------------------------------------- diff --git a/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/DefaultDynamicValue.java b/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/DefaultDynamicValue.java new file mode 100644 index 0000000..2f051b3 --- /dev/null +++ b/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/DefaultDynamicValue.java @@ -0,0 +1,498 @@ +/* + * 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.inject.internal; + +import org.apache.tamaya.ConfigException; +import org.apache.tamaya.Configuration; +import org.apache.tamaya.TypeLiteral; +import org.apache.tamaya.inject.api.BaseDynamicValue; +import org.apache.tamaya.inject.api.DynamicValue; +import org.apache.tamaya.inject.api.InjectionUtils; +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.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.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +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.IMMEDEATE); + } + + public static DynamicValue of(Field annotatedField, Configuration configuration, LoadPolicy loadPolicy) { + return of(annotatedField, configuration, loadPolicy, UpdatePolicy.IMMEDEATE); + } + + 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.IMMEDEATE); + } + + 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.IMMEDEATE); + } + + 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: + commit(); + break; + case EXPLCIT: + 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) { + ConversionContext ctx = new ConversionContext.Builder(key, targetType).build(); + if (propertyConverter == null) { + value = configuration.get(key, targetType); + } else { + String source = configuration.get(key); + 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/fe7cd8f1/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/InjectionHelper.java ---------------------------------------------------------------------- diff --git a/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/InjectionHelper.java b/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/InjectionHelper.java new file mode 100644 index 0000000..305a660 --- /dev/null +++ b/modules/injection/standalone/src/main/java/org/apache/tamaya/inject/internal/InjectionHelper.java @@ -0,0 +1,244 @@ +/* + * 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.inject.internal; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.tamaya.ConfigException; +import org.apache.tamaya.Configuration; +import org.apache.tamaya.ConfigurationProvider; +import org.apache.tamaya.TypeLiteral; +import org.apache.tamaya.events.ConfigEventManager; +import org.apache.tamaya.events.spi.BaseConfigEvent; +import org.apache.tamaya.inject.api.Config; +import org.apache.tamaya.inject.api.ConfigDefaultSections; +import org.apache.tamaya.inject.api.InjectionUtils; +import org.apache.tamaya.inject.api.WithPropertyConverter; +import org.apache.tamaya.inject.spi.ConfiguredType; +import org.apache.tamaya.resolver.spi.ExpressionEvaluator; +import org.apache.tamaya.spi.ConfigurationContext; +import org.apache.tamaya.spi.ConversionContext; +import org.apache.tamaya.spi.PropertyConverter; +import org.apache.tamaya.spi.ServiceContextManager; + + +/** + * Utility class containing several aspects used in this module. + */ +@SuppressWarnings("unchecked") +final class InjectionHelper { + + private static final Logger LOG = Logger.getLogger(InjectionHelper.class.getName()); + + private static final boolean RESOLUTION_MODULE_LOADED = checkResolutionModuleLoaded(); + + private static final boolean EVENTS_AVAILABLE = checkForEvents(); + + private static boolean checkForEvents() { + try{ + Class.forName("org.apache.tamaya.events.FrozenConfiguration"); + LOG.info("Detected tamaya-events is loaded, will trigger ConfigEvents..."); + return true; + } catch(Exception e){ + LOG.info("Detected tamaya-events not found, will not trigger any ConfigEvents..."); + return false; + } + } + + private static boolean checkResolutionModuleLoaded() { + try { + Class.forName("org.apache.tamaya.resolver.internal.DefaultExpressionEvaluator"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + + private InjectionHelper() { + } + + /** + * Internally evaluated the current valid configuration keys based on the given annotations present. + * @param method the method + * @return the keys to be returned, or null. + */ + public static String getConfigValue(Method method, Configuration config) { + return getConfigValue(method, null, config); + } + + /** + * Internally evaluated the current valid configuration keys based on the given annotations present. + * @param method the method + * @param retKey the array to return the key found, or null. + * @return the keys to be returned, or null. + */ + public static String getConfigValue(Method method, String[] retKey, Configuration config) { + ConfigDefaultSections areasAnnot = method.getDeclaringClass().getAnnotation(ConfigDefaultSections.class); + return getConfigValueInternal(method, areasAnnot, retKey, config); + } + + /** + * Internally evaluated the current valid configuration keys based on the given annotations present. + * @param field the field + * @return the keys to be returned, or null. + */ + public static String getConfigValue(Field field, Configuration config) { + return getConfigValue(field, null, config); + } + + /** + * Internally evaluated the current valid configuration keys based on the given annotations present. + * @param field the field + * @param retKey the array to return the key found, or null. + * @return the keys to be returned, or null. + */ + public static String getConfigValue(Field field, String[] retKey, Configuration config) { + ConfigDefaultSections areasAnnot = field.getDeclaringClass().getAnnotation(ConfigDefaultSections.class); + return getConfigValueInternal(field, areasAnnot, retKey, config); + } + + /** + * Internally evaluated the current valid configuration keys based on the given annotations present. + * + * @return the keys to be returned, or null. + */ + private static String getConfigValueInternal(AnnotatedElement element, ConfigDefaultSections areasAnnot, String[] retKey, Configuration config) { + Config prop = element.getAnnotation(Config.class); + List<String> keys; + if (prop == null) { + keys = InjectionUtils.evaluateKeys((Member) element, areasAnnot); + } else { + keys = InjectionUtils.evaluateKeys((Member) element, areasAnnot, prop); + } + String configValue = evaluteConfigValue(keys, retKey, config); + if (configValue == null) { + if(prop==null || prop.defaultValue().isEmpty()){ + return null; + } + return prop.defaultValue(); + } + return configValue; + } + + + private static String evaluteConfigValue(List<String> keys, String[] retKey, Configuration config) { + String configValue = null; + for (String key : keys) { + configValue = config.get(key); + if (configValue != null) { + if(retKey!=null && retKey.length>0){ + retKey[0] = key; + } + break; + } + } + return configValue; + } + + + @SuppressWarnings("rawtypes") + public static <T> T adaptValue(AnnotatedElement element, TypeLiteral<T> targetType, String key, String configValue) { + // Check for adapter/filter + T adaptedValue = null; + WithPropertyConverter converterAnnot = element.getAnnotation(WithPropertyConverter.class); + Class<? extends PropertyConverter<T>> converterType; + if (converterAnnot != null) { + converterType = (Class<? extends PropertyConverter<T>>) converterAnnot.value(); + if (!converterType.getName().equals(WithPropertyConverter.class.getName())) { + try { + // TODO cache here... + ConversionContext ctx = new ConversionContext.Builder(key,targetType) + .setAnnotatedElement(element).build(); + + PropertyConverter<T> converter = PropertyConverter.class.cast(converterType.newInstance()); + adaptedValue = converter.convert(configValue, ctx); + } catch (Exception e) { + LOG.log(Level.SEVERE, "Failed to convert using explicit PropertyConverter on " + element + + ", trying default conversion.", e); + } + } + } + if (adaptedValue != null) { + return adaptedValue; + } + if (String.class == targetType.getType()) { + return (T) configValue; + } else{ + if(configValue==null) { + return null; + } + ConfigurationContext configContext = ConfigurationProvider.getConfiguration().getContext(); + List<PropertyConverter<T>> converters = configContext + .getPropertyConverters(targetType); + ConversionContext ctx = new ConversionContext.Builder(ConfigurationProvider.getConfiguration(), + configContext, key, targetType).setAnnotatedElement(element).build(); + for (PropertyConverter<T> converter : converters) { + adaptedValue = converter.convert(configValue, ctx); + if (adaptedValue != null) { + return adaptedValue; + } + } + } + throw new ConfigException("Non convertible property type: " + element); + } + + /** + * Method that allows to statically check, if the resolver module is loaded. If the module is loaded + * value expressions are automatically forwarded to the resolver module for resolution. + * + * @return true, if the resolver module is on the classpath. + */ + public static boolean isResolutionModuleLoaded() { + return RESOLUTION_MODULE_LOADED; + } + + /** + * Evaluates the given expression. + * + * @param expression the expression, not null. + * @return the evaluated expression. + */ + public static String evaluateValue(String expression) { + if (!RESOLUTION_MODULE_LOADED) { + return expression; + } + ExpressionEvaluator evaluator = ServiceContextManager.getServiceContext().getService(ExpressionEvaluator.class); + if (evaluator != null) { + return evaluator.evaluateExpression("<injection>", expression, true); + } + return expression; + } + + /** + * This method distributes the configuration event, if the Tamaya event module is accessible. + * When Tamaya events are not available, the call simply returns. + * @param event the event to be distributed, not null. + */ + static void sendConfigurationEvent(ConfiguredType event) { + if(EVENTS_AVAILABLE){ + ConfigEventManager.fireEvent(new BaseConfigEvent<ConfiguredType>(event, ConfiguredType.class) {}); + } + } +}