http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/b56817f7/dormant/modules/metamodels/simple/src/main/resources/META-INF/services/org.apache.tamaya.core.spi.ConfigurationProviderSpi ---------------------------------------------------------------------- diff --git a/dormant/modules/metamodels/simple/src/main/resources/META-INF/services/org.apache.tamaya.core.spi.ConfigurationProviderSpi b/dormant/modules/metamodels/simple/src/main/resources/META-INF/services/org.apache.tamaya.core.spi.ConfigurationProviderSpi deleted file mode 100644 index b3a2634..0000000 --- a/dormant/modules/metamodels/simple/src/main/resources/META-INF/services/org.apache.tamaya.core.spi.ConfigurationProviderSpi +++ /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.metamodel.simple.SimpleConfigProvider \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/b56817f7/dormant/modules/pom.xml ---------------------------------------------------------------------- diff --git a/dormant/modules/pom.xml b/dormant/modules/pom.xml deleted file mode 100644 index e012023..0000000 --- a/dormant/modules/pom.xml +++ /dev/null @@ -1,60 +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. ---> -<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</groupId> - <artifactId>tamaya-all</artifactId> - <version>0.1-SNAPSHOT</version> - <relativePath>..</relativePath> - </parent> - <artifactId>tamaya-ext-all</artifactId> - <groupId>org.apache.tamaya.ext</groupId> - <name>Apache Tamaya Modules</name> - <packaging>pom</packaging> - - <properties> - <github.global.server>github</github.global.server> - <jdkVersion>1.8</jdkVersion> - <maven.compile.targetLevel>${jdkVersion}</maven.compile.targetLevel> - <maven.compile.sourceLevel>${jdkVersion}</maven.compile.sourceLevel> - </properties> - - <dependencies> - <dependency> - <groupId>org.apache.tamaya</groupId> - <artifactId>tamaya-core</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>org.apache.openejb</groupId> - <artifactId>mbean-annotation-api</artifactId> - <version>4.7.1</version> - </dependency> - </dependencies> - - <modules> - <module>metamodels</module> - <module>integration</module> - <!-- <module>environment</module> --> - </modules> - -</project> http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/b56817f7/modules/injection/pom.xml ---------------------------------------------------------------------- diff --git a/modules/injection/pom.xml b/modules/injection/pom.xml new file mode 100644 index 0000000..90f0b59 --- /dev/null +++ b/modules/injection/pom.xml @@ -0,0 +1,40 @@ +<!-- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy current the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.tamaya.integration</groupId> + <artifactId>tamaya-extensions-all</artifactId> + <version>0.2-SNAPSHOT</version> + <relativePath>..</relativePath> + </parent> + <artifactId>tamaya-injection</artifactId> + <name>Apache Tamaya Injection Support</name> + <packaging>jar</packaging> + + <dependencies> + <dependency> + <groupId>org.apache.tamaya</groupId> + <artifactId>tamaya-api</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> +</project> http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/b56817f7/modules/injection/src/main/java/org/apache/tamaya/inject/ConfiguredProperties.java ---------------------------------------------------------------------- diff --git a/modules/injection/src/main/java/org/apache/tamaya/inject/ConfiguredProperties.java b/modules/injection/src/main/java/org/apache/tamaya/inject/ConfiguredProperties.java new file mode 100644 index 0000000..e1d773d --- /dev/null +++ b/modules/injection/src/main/java/org/apache/tamaya/inject/ConfiguredProperties.java @@ -0,0 +1,41 @@ +/* + * 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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation container to enable injection current multiple {@link org.apache.tamaya.annotation.ConfiguredProperty} + * annotations. Hereby the ordering current annotations imply the defaulting. The first keys that + * could be resolved successfully in the chain current annotations will be used. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = { ElementType.FIELD, ElementType.METHOD }) +public @interface ConfiguredProperties { + + /** + * Get the different configuration keys to be looked up, in order current precedence. The first non null keys + * found will be used. + */ + ConfiguredProperty[] value() default {}; + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/b56817f7/modules/injection/src/main/java/org/apache/tamaya/inject/ConfiguredProperty.java ---------------------------------------------------------------------- diff --git a/modules/injection/src/main/java/org/apache/tamaya/inject/ConfiguredProperty.java b/modules/injection/src/main/java/org/apache/tamaya/inject/ConfiguredProperty.java new file mode 100644 index 0000000..21d4e3a --- /dev/null +++ b/modules/injection/src/main/java/org/apache/tamaya/inject/ConfiguredProperty.java @@ -0,0 +1,87 @@ +/* + * 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.annotation; + +import java.lang.annotation.*; + +/** + * Annotation to enable injection current a configured property or define the returned data for + * a configuration template method. Hereby this annotation can be used in multiple ways and combined + * with other annotations such as {@link org.apache.tamaya.annotation.DefaultValue}, + * {@link org.apache.tamaya.annotation.WithLoadPolicy}, {@link org.apache.tamaya.annotation.WithConfig}, + * {@link org.apache.tamaya.annotation.WithConfigOperator}, {@link WithPropertyAdapter}. + * + * Below the most simple variant current a configured class is given: + * {@code + * pubic class ConfiguredItem{ + * + * @ConfiguredProperty + * private String aValue; + * } + * When this class is configured, e.g. by passing it to {@link org.apache.tamaya.Configuration#configure(Object)}, + * the following is happening: + * <ul> + * <li>The current valid Configuration is evaluated by calling {@code Configuration cfg = Configuration.current();}</li> + * <li>The current property String keys is evaluated by calling {@code cfg.get("aValue");}</li> + * <li>if not successful, an error is thrown ({@link org.apache.tamaya.ConfigException}.</li> + * <li>On success, since no type conversion is involved, the keys is injected.</li> + * <li>The configured bean is registered as a weak change listener in the config system's underlying + * configuration, so future config changes can be propagated (controlled by {@link org.apache.tamaya.annotation.WithLoadPolicy} + * annotations).</li> + * </ul> + * + * In the next example we explicitly define the property keys: + * {@code + * pubic class ConfiguredItem{ + * + * @ConfiguredProperty + * @ConfiguredProperty({"a.b.value", "a.b.deprecated.keys", "${env:java.version}"}) + * @ConfiguredProperty(configuration={"a", "b"} + * @ConfiguredProperty(configuration={"a", "b", keys={"a.b.keys", "a.b.deprecated.keys", "${env:java.version}"}} + * private String aValue; + * } + * + * Within this example we evaluate multiple possible keys. Evaluation is aborted if a key could be successfully + * resolved. Hereby the ordering current the annotations define the ordering current resolution, so in the example above + * resolution equals to {@code "aValue", "a.b.keys", "a.b.deprecated.keys"}. If no keys could be read + * fromMap the configuration, it uses the keys fromMap the {@code DefaultValue} annotation. Interesting here + * is that this keys is not static, it is evaluated by calling + * {@link org.apache.tamaya.Configuration#evaluateValue(String, org.apache.tamaya.Configuration...)}. + */ +@Repeatable(ConfiguredProperties.class) +@Retention(RetentionPolicy.RUNTIME) +@Target(value = { ElementType.FIELD, ElementType.METHOD }) +public @interface ConfiguredProperty { + + /** + * Annotation to reference an explicit {@link org.apache.tamaya.Configuration} to be used to + * resolve the required properties. the configured keys is passed to {@code Configuration.current(String)} + * to evaluate the required configuration required. + * @return the configurations to be looked up for the given keys. + */ + String config() default ""; + + /** + * Get the property names to be used. Hereby the first non null keys evaluated is injected as property keys. + * + * @return the property names, not null. If missing the field or method name being injected is used by default. + */ + String[] keys() default {}; + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/b56817f7/modules/injection/src/main/java/org/apache/tamaya/inject/DefaultAreas.java ---------------------------------------------------------------------- diff --git a/modules/injection/src/main/java/org/apache/tamaya/inject/DefaultAreas.java b/modules/injection/src/main/java/org/apache/tamaya/inject/DefaultAreas.java new file mode 100644 index 0000000..63ea137 --- /dev/null +++ b/modules/injection/src/main/java/org/apache/tamaya/inject/DefaultAreas.java @@ -0,0 +1,43 @@ +/* + * 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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to control injection and resolution current a configured bean. The configuration keys + * to be resolved are basically determined by the {@link org.apache.tamaya.annotation.ConfiguredProperty} + * annotation(s). Nevertheless these annotations can also have relative key names. This annotation allows + * to define a configuration area that is prefixed to all relative configuration keys within the + * corresponding class/template interface. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = { ElementType.TYPE }) +public @interface DefaultAreas { + + /** + * Allows to declare an operator that should be applied before injecting values into the bean. + * @return the operator class to be used. + */ + String[] value(); + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/b56817f7/modules/injection/src/main/java/org/apache/tamaya/inject/DefaultValue.java ---------------------------------------------------------------------- diff --git a/modules/injection/src/main/java/org/apache/tamaya/inject/DefaultValue.java b/modules/injection/src/main/java/org/apache/tamaya/inject/DefaultValue.java new file mode 100644 index 0000000..c4b2e3a --- /dev/null +++ b/modules/injection/src/main/java/org/apache/tamaya/inject/DefaultValue.java @@ -0,0 +1,41 @@ +/* + * 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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to define a default keys to be returned, when no configured keys could be + * determined for a property/template accessor. The keys hereby can also contain a + * dynamic expression that is evaluated by the configuration system. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = { ElementType.FIELD, ElementType.METHOD }) +public @interface DefaultValue { + + /** + * The default keys to be injected, if no such configuration entry was found. If keys was found and no default + * is defined, it is handled as a deployment error. + */ + String value() default ""; + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/b56817f7/modules/injection/src/main/java/org/apache/tamaya/inject/DynamicValue.java ---------------------------------------------------------------------- diff --git a/modules/injection/src/main/java/org/apache/tamaya/inject/DynamicValue.java b/modules/injection/src/main/java/org/apache/tamaya/inject/DynamicValue.java new file mode 100644 index 0000000..b8e0cf5 --- /dev/null +++ b/modules/injection/src/main/java/org/apache/tamaya/inject/DynamicValue.java @@ -0,0 +1,500 @@ +/* + * 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; + +import java.beans.PropertyChangeEvent; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.ref.WeakReference; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +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 org.apache.tamaya.DynamicValue.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> + */ +public final class DynamicValue<T> implements Serializable{ + + /** + * Policy to control how new values are applied to this instance. + */ + enum UpdatePolicy{ + /** New values are applied immedately and registered listeners are informed about the change. */ + IMMEDIATE, + /** New values or not applied, but stored in the newValue property. Explcit call to #commit + of #commitAndGet are required to accept the change and inform the listeners about the change. + */ + EXPLCIT, + /** + * New values are always immedately discarded. + */ + NEVER, + /** + * Changes are logged before the are discarded. + */ + LOG_AND_DISCARD + } + + + /** The property name of the entry. */ + private String propertyName; + /** + * Policy that defines how new values are applied, be default it is applied initially once, but never updated anymore. + */ + private UpdatePolicy updatePolicy = UpdatePolicy.NEVER; + /** The current value, never null. */ + private transient Optional<T> value; + /** The new value, or null. */ + private transient Optional<T> newValue; + /** List of listeners that listen for changes. */ + private transient WeakList<Consumer<PropertyChangeEvent>> listeners; + + /** + * Returns an empty {@code Optional} instance. No value is present for this + * Optional. + * + * @apiNote Though it may be tempting to do so, avoid testing if an object + * is empty by comparing with {@code ==} against instances returned by + * {@code Option.empty()}. There is no guarantee that it is a singleton. + * Instead, use {@link #isPresent()}. + * + * @param <T> Type of the non-existent value + * @return an empty {@code Optional} + */ + public static <T> DynamicValue<T> empty(String propertyName) { + DynamicValue v = new DynamicValue<T>(propertyName, null); + return v; + } + + /** + * Constructor. + * @param propertyName the name of the value in the format {@code <configName>:<propertyName>}.</config> + * @param item the initial value. + */ + private DynamicValue(String propertyName, Optional<T> item){ + this.propertyName = Objects.requireNonNull(propertyName); + this.value = item; + } + + /** + * Creates a new instance. + * @param propertyName the name of the value in the format {@code <configName>:<propertyName>}.</config> + * @param value the initial value, not null. + * @param <T> the type + * @return a new instance, never null + */ + public static <T> DynamicValue<T> of(String propertyName, T value){ + return new DynamicValue(propertyName, Optional.of(value)); + } + + /** + * Creates a new instance. + * @param propertyName the name of the value in the format {@code <configName>:<propertyName>}.</config> + * @param value the initial value + * @param <T> the target type. + * @return a new instance, never null + */ + public static <T> DynamicValue<T> ofNullable(String propertyName, T value){ + return value == null ? empty(propertyName) : of(propertyName, value); + } + + /** + * Performs a commit, if necessary and returns the current value. + * otherwise throws {@code ConfigException}. + * + * @return the non-null value held by this {@code Optional} + * @throws org.apache.tamaya.ConfigException if there is no value present + * + * @see DynamicValue#isPresent() + */ + public T commitAndGet(){ + commit(); + return get(); + } + + /** + * 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(){ + synchronized (value){ + if(newValue!=null){ + PropertyChangeEvent evt = new PropertyChangeEvent(this, propertyName, value.orElse(null), newValue.orElse(null)); + value = newValue; + newValue = null; + for(Consumer<PropertyChangeEvent> consumer: listeners.get()){ + consumer.accept(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; + } + + /** + * Add a listener to be called as weak reference, when this value has been changed. + * @param l the listner, not null + */ + public void addListener(Consumer<PropertyChangeEvent> 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(Consumer<PropertyChangeEvent> l) { + if(listeners!=null){ + listeners.remove(l); + } + } + + /** + * If a value is present in this {@code ConfiguredValue}, returns the value, + * otherwise throws {@code ConfigException}. + * + * @return the non-null value held by this {@code Optional} + * @throws org.apache.tamaya.ConfigException if there is no value present + * + * @see DynamicValue#isPresent() + */ + public T get() { + return value.get(); + } + + /** + * Method to apply a new value. Depending on the {@link org.apache.tamaya.DynamicValue.UpdatePolicy} + * the value is immediately or deferred visible (or it may even be ignored completely). + * @param newValue the new value, may also be null. + */ + public void setNewValue(T newValue){ + switch(this.updatePolicy){ + case IMMEDIATE: + this.newValue = Optional.ofNullable(newValue); + commit(); + break; + case EXPLCIT: + this.newValue = Optional.ofNullable(newValue); + break; + case LOG_AND_DISCARD: + Logger.getLogger(getClass().getName()).info("Discard change on " + this + ", newValue="+newValue); + this.newValue = null; + break; + case NEVER: + this.newValue = null; + break; + } + + } + + /** + * Sets a new {@link org.apache.tamaya.DynamicValue.UpdatePolicy}. + * @param updatePolicy the new policy, not null. + */ + public void setUpdatePolicy(UpdatePolicy updatePolicy){ + this.updatePolicy = Objects.requireNonNull(updatePolicy); + } + + /** + * Access a new value that has not yet been committed. + * @return the uncommitted new value, or null. + */ + public T getNewValue(){ + Optional<T> nv = newValue; + if(nv!=null){ + return nv.orElse(null); + } + return null; + } + + /** + * Return {@code true} if there is a value present, otherwise {@code false}. + * + * @return {@code true} if there is a value present, otherwise {@code false} + */ + public boolean isPresent() { + return value.isPresent(); + } + + /** + * If a value is present, invoke the specified consumer with the value, + * otherwise do nothing. + * + * @param consumer block to be executed if a value is present + * @throws NullPointerException if value is present and {@code consumer} is + * null + */ + public void ifPresent(Consumer<? super T> consumer) { + value.ifPresent(consumer); + } + + /** + * If a value is present, and the value matches the given predicate, + * return an {@code Optional} describing the value, otherwise return an + * empty {@code Optional}. + * + * @param predicate a predicate to apply to the value, if present + * @return an {@code Optional} describing the value of this {@code Optional} + * if a value is present and the value matches the given predicate, + * otherwise an empty {@code Optional} + * @throws NullPointerException if the predicate is null + */ + public DynamicValue<T> filter(Predicate<? super T> predicate) { + Objects.requireNonNull(predicate); + if (!isPresent()) + return this; + else + return predicate.test(value.get()) ? this : empty(propertyName); + } + + /** + * If a value is present, apply the provided mapping function to it, + * and if the result is non-null, return an {@code Optional} describing the + * result. Otherwise return an empty {@code Optional}. + * + * @apiNote This method supports post-processing on optional values, without + * the need to explicitly check for a return status. For example, the + * following code traverses a stream of file names, selects one that has + * not yet been processed, and then opens that file, returning an + * {@code Optional<FileInputStream>}: + * + * <pre>{@code + * Optional<FileInputStream> fis = + * names.stream().filter(name -> !isProcessedYet(name)) + * .findFirst() + * .map(name -> new FileInputStream(name)); + * }</pre> + * + * Here, {@code findFirst} returns an {@code Optional<String>}, and then + * {@code map} returns an {@code Optional<FileInputStream>} for the desired + * file if one exists. + * + * @param <U> The type of the result of the mapping function + * @param mapper a mapping function to apply to the value, if present + * @return an {@code Optional} describing the result of applying a mapping + * function to the value of this {@code Optional}, if a value is present, + * otherwise an empty {@code Optional} + * @throws NullPointerException if the mapping function is null + */ + public <U> DynamicValue<U> map(Function<? super T, ? extends U> mapper) { + Objects.requireNonNull(mapper); + if (!isPresent()) + return empty(propertyName); + else { + return DynamicValue.ofNullable(propertyName, mapper.apply(value.get())); + } + } + + /** + * If a value is present, apply the provided {@code Optional}-bearing + * mapping function to it, return that result, otherwise return an empty + * {@code Optional}. This method is similar to {@link #map(Function)}, + * but the provided mapper is one whose result is already an {@code Optional}, + * and if invoked, {@code flatMap} does not wrap it with an additional + * {@code Optional}. + * + * @param <U> The type parameter to the {@code Optional} returned by + * @param mapper a mapping function to apply to the value, if present + * the mapping function + * @return the result of applying an {@code Optional}-bearing mapping + * function to the value of this {@code Optional}, if a value is present, + * otherwise an empty {@code Optional} + * @throws NullPointerException if the mapping function is null or returns + * a null result + */ + public <U> DynamicValue<U> flatMap(Function<? super T, DynamicValue<U>> mapper) { + Objects.requireNonNull(mapper); + if (!isPresent()) + return empty(propertyName); + else { + return Objects.requireNonNull(mapper.apply(value.get())); + } + } + + /** + * Return the value if present, otherwise return {@code other}. + * + * @param other the value to be returned if there is no value present, may + * be null + * @return the value, if present, otherwise {@code other} + */ + public T orElse(T other) { + return value.orElse(other); + } + + /** + * Return the value if present, otherwise invoke {@code other} and return + * the result of that invocation. + * + * @param other a {@code Supplier} whose result is returned if no value + * is present + * @return the value if present otherwise the result of {@code other.get()} + * @throws NullPointerException if value is not present and {@code other} is + * null + */ + public T orElseGet(Supplier<? extends T> other) { + return value.orElseGet(other); + } + + /** + * Return the contained value, if present, otherwise throw an exception + * to be created by the provided supplier. + * + * @apiNote A method reference to the exception constructor with an empty + * argument list can be used as the supplier. For example, + * {@code IllegalStateException::new} + * + * @param <X> Type of the exception to be thrown + * @param exceptionSupplier The supplier which will return the exception to + * be thrown + * @return the present value + * @throws X if there is no value present + * @throws NullPointerException if no value is present and + * {@code exceptionSupplier} is null + */ + public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X { + return value.orElseThrow(exceptionSupplier); + } + + /** + * Converts the instance to an {@link java.util.Optional} instance. + * @return the corresponding Optional value. + */ + public Optional<T> toOptional(){ + return value; + } + + /** + * 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(updatePolicy); + if(isPresent()) { + oos.writeObject(this.value.get()); + } + else{ + oos.writeObject(null); + } + } + + /** + * 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 = Optional.of((T) ois.readObject()); + } + newValue = null; + } + + + /** + * Simple helper that allows keeping the listeners registered as weak references, hereby avoiding any + * memory leaks. + * @param <T> the type + */ + private class WeakList<T>{ + List<WeakReference<T>> refs = new LinkedList<>(); + + /** + * Adds a new instance. + * @param t the new instance, not null. + */ + void add(T t){ + refs.add(new WeakReference(t)); + } + + /** + * Removes a instance. + * @param t the instance to be removed. + */ + void remove(T t){ + synchronized (refs){ + for(Iterator<WeakReference<T>> iterator = refs.iterator();iterator.hasNext();){ + WeakReference<T> ref = iterator.next(); + T 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<T> get() { + synchronized (refs) { + List<T> res = new ArrayList<>(); + for (Iterator<WeakReference<T>> iterator = refs.iterator(); iterator.hasNext(); ) { + WeakReference<T> ref = iterator.next(); + T instance = ref.get(); + if(instance==null){ + iterator.remove(); + } + else{ + res.add(instance); + } + } + return res; + } + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/b56817f7/modules/injection/src/main/java/org/apache/tamaya/inject/LoadPolicy.java ---------------------------------------------------------------------- diff --git a/modules/injection/src/main/java/org/apache/tamaya/inject/LoadPolicy.java b/modules/injection/src/main/java/org/apache/tamaya/inject/LoadPolicy.java new file mode 100644 index 0000000..116a2c1 --- /dev/null +++ b/modules/injection/src/main/java/org/apache/tamaya/inject/LoadPolicy.java @@ -0,0 +1,48 @@ +/* + * 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.annotation; + +/** + * Available policies that describe how changes affecting configured values are published/reinjected. + * The policy also affects the cases were any configured listeners/listener methods are called for + * propagation current configuration changes. + */ +public enum LoadPolicy { + /** + * The configuration keys is evaluated once, when the owning component is loaded/configured, but never updated later. + */ + INITIAL, + /** + * The configuration keys is evaluated exactly once on its first use lazily, but never updated later. + * This feature is not applicable on field injection, but only on configuration template methods. + */ + LAZY, + /** + * The configuration keys is evaluated once, when the owning component is loaded/configured. + * Later changes on this configuration entry will be reinjected/updated and additionally triggered + * as {@link java.beans.PropertyChangeEvent}. + */ + MANAGED, + /** + * The configuration keys is evaluated once, when the owning component is loaded/configured. + * Later changes on this configuration entry will be reinjected/updated, but no {@link java.beans.PropertyChangeEvent} + * will be triggered. + */ + SILENT +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/b56817f7/modules/injection/src/main/java/org/apache/tamaya/inject/NoConfig.java ---------------------------------------------------------------------- diff --git a/modules/injection/src/main/java/org/apache/tamaya/inject/NoConfig.java b/modules/injection/src/main/java/org/apache/tamaya/inject/NoConfig.java new file mode 100644 index 0000000..845ec4c --- /dev/null +++ b/modules/injection/src/main/java/org/apache/tamaya/inject/NoConfig.java @@ -0,0 +1,32 @@ +/* + * 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.annotation; + +import java.lang.annotation.*; + +/** + * This is a small marker annotations to inform Tamaya that the annotated element should never be injected with + * configured data. This is useful because by default Tamaya tries to lookup and inject configuration also by + * using property or method names without annotations. With that annotation none of these will be happen. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = { ElementType.FIELD, ElementType.METHOD }) +public @interface NoConfig { + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/b56817f7/modules/injection/src/main/java/org/apache/tamaya/inject/ObservesConfigChange.java ---------------------------------------------------------------------- diff --git a/modules/injection/src/main/java/org/apache/tamaya/inject/ObservesConfigChange.java b/modules/injection/src/main/java/org/apache/tamaya/inject/ObservesConfigChange.java new file mode 100644 index 0000000..ef92b25 --- /dev/null +++ b/modules/injection/src/main/java/org/apache/tamaya/inject/ObservesConfigChange.java @@ -0,0 +1,38 @@ +/* + * 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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to annotate a method on a class to be informed on config changes. + * The exact behaviour, when configuration change events are sent can be configured + * on each configured property/method by adding the {@link org.apache.tamaya.annotation.WithLoadPolicy} + * annotation. By default listeners are informed on all changes of configurations that were used as + * input configurations for configuring a class/instance. Additionally {@link org.apache.tamaya.annotation.ConfiguredProperty} + * annotations can be added that allows to constrain changes to some limited properties. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = { ElementType.METHOD }) +public @interface ObservesConfigChange { + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/b56817f7/modules/injection/src/main/java/org/apache/tamaya/inject/WithConfigOperator.java ---------------------------------------------------------------------- diff --git a/modules/injection/src/main/java/org/apache/tamaya/inject/WithConfigOperator.java b/modules/injection/src/main/java/org/apache/tamaya/inject/WithConfigOperator.java new file mode 100644 index 0000000..9f6c4f5 --- /dev/null +++ b/modules/injection/src/main/java/org/apache/tamaya/inject/WithConfigOperator.java @@ -0,0 +1,45 @@ +/* + * 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.annotation; + +import org.apache.tamaya.Configuration; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.function.UnaryOperator; + +/** + * Annotation to define an configuration operator to be used before accessing a configured keys. + * This allows filtering current configuration, e.g. for realizing views or ensuring security + * constraints. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = { ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) +public @interface WithConfigOperator { + + /** + * Define a custom adapter that should be used to adapt the configuration entry injected. This overrides any + * general org.apache.tamaya.core.internal registered. If no adapter is defined (default) and no corresponding adapter is + * registered, it is handled as a deployment error. + */ + Class<? extends UnaryOperator<Configuration>> value(); + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/b56817f7/modules/injection/src/main/java/org/apache/tamaya/inject/WithLoadPolicy.java ---------------------------------------------------------------------- diff --git a/modules/injection/src/main/java/org/apache/tamaya/inject/WithLoadPolicy.java b/modules/injection/src/main/java/org/apache/tamaya/inject/WithLoadPolicy.java new file mode 100644 index 0000000..e469f5a --- /dev/null +++ b/modules/injection/src/main/java/org/apache/tamaya/inject/WithLoadPolicy.java @@ -0,0 +1,40 @@ +/* + * 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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to define how config changes are handled for a type or per property/template method. + * @see org.apache.tamaya.annotation.LoadPolicy + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = { ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) +public @interface WithLoadPolicy { + + /** + * The load policy to be used. If this annotation is present a load policy must be defined. + * @return The load policy to be used, not null. + */ + LoadPolicy value(); + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/b56817f7/modules/injection/src/main/java/org/apache/tamaya/inject/WithPropertyAdapter.java ---------------------------------------------------------------------- diff --git a/modules/injection/src/main/java/org/apache/tamaya/inject/WithPropertyAdapter.java b/modules/injection/src/main/java/org/apache/tamaya/inject/WithPropertyAdapter.java new file mode 100644 index 0000000..fa9cfdf --- /dev/null +++ b/modules/injection/src/main/java/org/apache/tamaya/inject/WithPropertyAdapter.java @@ -0,0 +1,45 @@ +/* + * 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.annotation; + +import org.apache.tamaya.PropertyAdapter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to define a type adapter to be used before injecting a configured keys, or for applying changes. + * This will override any other adapter for performing the type conversion before + * injecting the field keys. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = { ElementType.FIELD, ElementType.METHOD }) +public @interface WithPropertyAdapter { + + /** + * Define a custom adapter or codec that should be used to adapt the configuration entry injected. This overrides any + * general org.apache.tamaya.core.internal registered. If no adapter is defined (default) and no corresponding adapter is + * registered, it is handled as a deployment error. + */ + @SuppressWarnings("rawtypes") + Class<? extends PropertyAdapter> value(); + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/b56817f7/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfigChangeCallbackMethod.java ---------------------------------------------------------------------- diff --git a/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfigChangeCallbackMethod.java b/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfigChangeCallbackMethod.java new file mode 100644 index 0000000..f929f8e --- /dev/null +++ b/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfigChangeCallbackMethod.java @@ -0,0 +1,67 @@ +/* + * 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.core.internal.inject; + +import org.apache.tamaya.core.properties.PropertyChangeSet; +import org.apache.tamaya.Configuration; + +import java.lang.reflect.Method; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This class holds a method object that is annotated to be a callback method to be called on configuration + * changes. + */ +public final class ConfigChangeCallbackMethod { + + private static final Logger LOG = Logger.getLogger(ConfigChangeCallbackMethod.class.getName()); + + private Method callbackMethod; + + public ConfigChangeCallbackMethod(Method callbackMethod) { + this.callbackMethod = Optional.of(callbackMethod).filter( + (m) -> void.class.equals(m.getReturnType()) && + m.getParameterCount() == 1 && + m.getParameterTypes()[0].equals(PropertyChangeSet.class)).get(); + } + + public Consumer<PropertyChangeSet> createConsumer(Object instance, Configuration... configurations){ + // TODO consider also environment ! + return event -> { + for(Configuration cfg:configurations){ + if(event.getPropertySource().getName().equals(cfg.getName())){ + return; + } + } + call(instance, event); + }; + } + + public void call(Object instance, PropertyChangeSet configChangeEvent) { + try { + callbackMethod.setAccessible(true); + callbackMethod.invoke(instance, configChangeEvent); + } catch (Exception e) { + LOG.log(Level.SEVERE, e, () -> "Error calling ConfigChange callback method " + callbackMethod.getDeclaringClass().getName() + '.' + callbackMethod.getName() + " on " + instance); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/b56817f7/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfigTemplateInvocationHandler.java ---------------------------------------------------------------------- diff --git a/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfigTemplateInvocationHandler.java b/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfigTemplateInvocationHandler.java new file mode 100644 index 0000000..ff2c309 --- /dev/null +++ b/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfigTemplateInvocationHandler.java @@ -0,0 +1,76 @@ +/* + * 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.core.internal.inject; + +import org.apache.tamaya.Configuration; +import org.apache.tamaya.core.internal.inject.ConfiguredType; +import org.apache.tamaya.core.internal.inject.InjectionUtils; + +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 { + + /* + TODO + the given method (in case of a template) can use different caching strategies: + 1) no caching (always evaluate the values completely) - slow. + 2) instance caching (a cache per instance). + 3) classloader caching... + 4) global shared cache. + */ + + + /** + * Any overriding configurations. + */ + private Configuration[] configurations; + /** + * The configured type. + */ + private ConfiguredType type; + + /** + * Creates a new handler instance. + * @param type the target type, not null. + * @param configurations overriding configurations to be used for evaluating the values for injection into {@code instance}, not null. + * If no such config is passed, the default configurationa provided by the current + * registered providers are used. + */ + public ConfigTemplateInvocationHandler(Class<?> type, Configuration... configurations) { + this.configurations = Objects.requireNonNull(configurations).clone(); + this.type = new ConfiguredType(Objects.requireNonNull(type)); + if (!type.isInterface()) { + throw new IllegalArgumentException("Can only proxy interfaces as configuration templates."); + } + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if ("toString".equals(method.getName())) { + return "Configured Proxy -> " + this.type.getType().getName(); + } + String configValue = InjectionUtils.getConfigValue(method, configurations); + return InjectionUtils.adaptValue(method, method.getReturnType(), configValue); + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/b56817f7/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfigurationInjector.java ---------------------------------------------------------------------- diff --git a/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfigurationInjector.java b/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfigurationInjector.java new file mode 100644 index 0000000..8a51375 --- /dev/null +++ b/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfigurationInjector.java @@ -0,0 +1,61 @@ +/* + * 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.core.internal.inject; + +import org.apache.tamaya.Configuration; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Simple injector singleton that also registers instances configured using weak references. + */ +@SuppressWarnings("rawtypes") +public final class ConfigurationInjector { + + private static final ConfigurationInjector INSTANCE = new ConfigurationInjector(); + + private Map<Class, ConfiguredType> configuredTypes = new ConcurrentHashMap<>(); + + /** + * 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 static ConfiguredType registerType(Class<?> type){ + return INSTANCE.configuredTypes.computeIfAbsent(type, ConfiguredType::new); + } + + /** + * 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 configurations Configuration instances that replace configuration served by services. This allows + * more easily testing and adaption. + */ + public static void configure(Object instance, Configuration... configurations){ + Class type = Objects.requireNonNull(instance).getClass(); + ConfiguredType configuredType = registerType(type); + Objects.requireNonNull(configuredType).configure(instance, configurations); + } + + + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/b56817f7/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfiguredField.java ---------------------------------------------------------------------- diff --git a/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfiguredField.java b/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfiguredField.java new file mode 100644 index 0000000..51c3904 --- /dev/null +++ b/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfiguredField.java @@ -0,0 +1,126 @@ +/* + * 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.core.internal.inject; + +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import org.apache.tamaya.ConfigException; +import org.apache.tamaya.Configuration; +import org.apache.tamaya.annotation.ConfiguredProperties; +import org.apache.tamaya.annotation.ConfiguredProperty; +import org.apache.tamaya.annotation.DefaultAreas; +import org.apache.tamaya.core.internal.Utils; + +/** + * 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 ConfiguredField { + + + /** + * The configured field instance. + */ + private Field annotatedField; + + /** + * Models a configured field and provides mechanisms for injection. + * + * @param field the field instance. + */ + public ConfiguredField(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. + * @param configurations Configuration instances that replace configuration served by services. This allows + * more easily testing and adaption. + * @throws ConfigException if evaluation or conversion failed. + */ + public void applyInitialValue(Object target, Configuration... configurations) throws ConfigException { + String configValue = InjectionUtils.getConfigValue(this.annotatedField, configurations); + applyValue(target, configValue, false, configurations); + } + + + /** + * This method reapplies a changed configuration keys to the field. + * + * @param target the target instance, not null. + * @param configValue the new keys to be applied, null will trigger the evaluation current the configured default keys. + * @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. + */ + public void applyValue(Object target, String configValue, boolean resolve, Configuration... configurations) throws ConfigException { + Objects.requireNonNull(target); + try { + if (resolve && configValue != null) { + // net step perform exression resolution, if any + configValue = Configuration.evaluateValue(configValue, configurations); + } + // Check for adapter/filter + Object value = InjectionUtils.adaptValue(this.annotatedField, this.annotatedField.getType(), configValue); + annotatedField.setAccessible(true); + annotatedField.set(target, value); + } catch (Exception e) { + throw new ConfigException("Failed to annotation configured field: " + this.annotatedField.getDeclaringClass() + .getName() + '.' + annotatedField.getName(), e); + } + } + + + /** + * This method checks if the given (qualified) configuration key is referenced fromMap this field. + * This is useful to determine, if a key changed in a configuration should trigger any change events + * on the related instances. + * + * @param key the (qualified) configuration key, not null. + * @return true, if the key is referenced. + */ + public boolean matchesKey(String configName, String key) { + Collection<ConfiguredProperty> configuredProperties = Utils.getAnnotations(this.annotatedField, ConfiguredProperty.class, + ConfiguredProperties.class ); + for(ConfiguredProperty prop: configuredProperties){ + String currentName = prop.config().trim(); + if(currentName.isEmpty()){ + if(!"default".equals(configName)){ + continue; + } + } + else if(!currentName.equals(configName)){ + continue; + } + DefaultAreas areasAnnot = this.annotatedField.getDeclaringClass().getAnnotation(DefaultAreas.class); + List<String> keys = InjectionUtils.evaluateKeys(this.annotatedField, areasAnnot, prop); + if( keys.contains(key)){ + return true; + } + } + return false; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/b56817f7/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfiguredSetterMethod.java ---------------------------------------------------------------------- diff --git a/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfiguredSetterMethod.java b/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfiguredSetterMethod.java new file mode 100644 index 0000000..90497ae --- /dev/null +++ b/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfiguredSetterMethod.java @@ -0,0 +1,136 @@ +/* + * 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.core.internal.inject; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; + +import org.apache.tamaya.core.properties.PropertyChangeSet; +import org.apache.tamaya.ConfigException; +import org.apache.tamaya.Configuration; +import org.apache.tamaya.annotation.*; +import org.apache.tamaya.core.internal.Utils; + +/** + * 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 { + + /** + * The configured field instance. + */ + private Method setterMethod; + + /** + * Models a configured field and provides mechanisms for injection. + * + * @param method the method instance. + */ + public ConfiguredSetterMethod(Method method) { + this.setterMethod = Optional.of(method).filter( + (m) -> void.class.equals(m.getReturnType()) && + m.getParameterCount() == 1).get(); + } + + public Consumer<PropertyChangeSet> createConsumer(Object instance, Configuration... configurations){ + // TODO consider environment as well + return event -> { + for(Configuration cfg:configurations){ + if(event.getPropertySource().getName().equals(cfg.getName())){ + // ignore these changes, since this config is overridden. + return; + } + } + String configValue = InjectionUtils.getConfigValue(setterMethod, configurations); + applyValue(instance,configValue, false, configurations); + }; + } + + + /** + * Evaluate the initial keys fromMap the configuration and applyChanges it to the field. + * + * @param target the target instance. + * @param configurations Configuration instances that replace configuration served by services. This allows + * more easily testing and adaption. + * @throws ConfigException if evaluation or conversion failed. + */ + public void applyInitialValue(Object target, Configuration... configurations) throws ConfigException { + String configValue = InjectionUtils.getConfigValue(this.setterMethod, configurations); + applyValue(target, configValue, false, configurations); + } + + /** + * This method reapplies a changed configuration keys to the field. + * + * @param target the target instance, not null. + * @param configValue the new keys to be applied, null will trigger the evaluation current the configured default keys. + * @param resolve set to true, if expression resolution should be applied on the keys passed. + * @throws org.apache.tamaya.ConfigException if the configuration required could not be resolved or converted. + */ + public void applyValue(Object target, String configValue, boolean resolve, Configuration... configurations) throws ConfigException { + Objects.requireNonNull(target); + try { + if (resolve && configValue != null) { + // net step perform exression resolution, if any + configValue = Configuration.evaluateValue(configValue, configurations); + } + // Check for adapter/filter + Object value = InjectionUtils.adaptValue(this.setterMethod, this.setterMethod.getParameterTypes()[0], configValue); + setterMethod.setAccessible(true); + setterMethod.invoke(target, value); + } catch (Exception e) { + throw new ConfigException("Failed to annotation configured method: " + this.setterMethod.getDeclaringClass() + .getName() + '.' + setterMethod.getName(), e); + } + } + + + + /** + * This method checks if the given (qualified) configuration key is referenced fromMap this field. + * This is useful to determine, if a key changed in a configuration should trigger any change events + * on the related instances. + * + * @param key the (qualified) configuration key, not null. + * @return true, if the key is referenced. + */ + public boolean matchesKey(String key) { + DefaultAreas areasAnnot = this.setterMethod.getDeclaringClass().getAnnotation(DefaultAreas.class); + Collection<ConfiguredProperty> configuredProperties = + Utils.getAnnotations(this.setterMethod, ConfiguredProperty.class, ConfiguredProperties.class); + for(ConfiguredProperty prop: configuredProperties) { + if (InjectionUtils.evaluateKeys(this.setterMethod, areasAnnot, prop).contains(key)) { + return true; + } + } + if (InjectionUtils.evaluateKeys(this.setterMethod, areasAnnot).contains(key)) { + return true; + } + return false; + } + + + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/b56817f7/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfiguredType.java ---------------------------------------------------------------------- diff --git a/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfiguredType.java b/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfiguredType.java new file mode 100644 index 0000000..879d54a --- /dev/null +++ b/modules/injection/src/main/java/org/apache/tamaya/inject/internal/ConfiguredType.java @@ -0,0 +1,222 @@ +/* + * 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.core.internal.inject; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.*; + +import org.apache.tamaya.core.properties.PropertyChangeSet; +import org.apache.tamaya.ConfigException; +import org.apache.tamaya.Configuration; +import org.apache.tamaya.PropertySource; +import org.apache.tamaya.annotation.*; +import org.apache.tamaya.core.internal.Utils; + +/** + * 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 ConfiguredType { + /** + * A list with all annotated instance variables. + */ + private List<ConfiguredField> configuredFields = new ArrayList<>(); + /** + * A list with all annotated methods (templates). + */ + private List<ConfiguredSetterMethod> configuredSetterMethods = new ArrayList<>(); + /** + * A list with all callback methods listening to config changes. + */ + private List<ConfigChangeCallbackMethod> callbackMethods = new ArrayList<>(); + /** + * The basic type. + */ + private 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 ConfiguredType(Class type) { + this.type = Objects.requireNonNull(type); + initFields(type); + initMethods(type); + } + + private void initFields(Class type) { + for (Field f : type.getDeclaredFields()) { + if (f.isAnnotationPresent(NoConfig.class)) { + continue; + } + try { + ConfiguredField configuredField = new ConfiguredField(f); + configuredFields.add(configuredField); + } catch (Exception e) { + throw new ConfigException("Failed to initialized configured field: " + + f.getDeclaringClass().getName() + '.' + f.getName(), e); + } + } + } + + private void initMethods(Class type) { + // TODO revisit this logic here... + for (Method m : type.getDeclaredMethods()) { + if (m.isAnnotationPresent(NoConfig.class)) { + continue; + } + ObservesConfigChange mAnnot = m.getAnnotation(ObservesConfigChange.class); + Collection<ConfiguredProperty> propertiesAnnots = Utils.getAnnotations(m, ConfiguredProperty.class, ConfiguredProperties.class); + if (type.isInterface()) { + // it is a template + if (mAnnot != null) { + if (m.isDefault()) { + addObserverMethod(m); + } + } else { + if (m.isDefault()) { + addPropertySetter(m, propertiesAnnots); + } + } + } else { + if (mAnnot != null) { + addObserverMethod(m); + } else { + addPropertySetter(m, propertiesAnnots); + } + } + } + } + + private boolean addPropertySetter(Method m, Collection<ConfiguredProperty> propertiesAnnots) { + if (!propertiesAnnots.isEmpty()) { + if (m.getParameterTypes().length == 0) { + // 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; + } + + + + private void addObserverMethod(Method m) { + if (m.getParameterTypes().length != 1) { + return; + } + if (!m.getParameterTypes()[0].equals(PropertyChangeSet.class)) { + return; + } + if (!void.class.equals(m.getReturnType())) { + return; + } + try { + this.callbackMethods.add(new ConfigChangeCallbackMethod(m)); + } catch (Exception e) { + throw new ConfigException("Failed to initialized configured callback method: " + + m.getDeclaringClass().getName() + '.' + m.getName(), e); + } + } + + + /** + * Method called to configure an instance. + * + * @param instance The instance to be configured. + * @param configurations Configuration instances that replace configuration served by services. This allows + * more easily testing and adaption. + */ + public void configure(Object instance, Configuration... configurations) { + for (ConfiguredField field : configuredFields) { + field.applyInitialValue(instance, configurations); + } + for (ConfiguredSetterMethod method : configuredSetterMethods) { + method.applyInitialValue(instance, configurations); + // TODO, if method should be recalled on changes, corresponding callbacks could be registered here + WeakConfigListenerManager.of().registerConsumer(instance, method.createConsumer(instance, configurations)); + } + // Register callbacks for this intance (weakly) + for (ConfigChangeCallbackMethod callback : callbackMethods) { + WeakConfigListenerManager.of().registerConsumer(instance, callback.createConsumer(instance, configurations)); + } + } + + + private String getName(Object source) { + if (source instanceof PropertySource) { + PropertySource ps = (PropertySource) source; + return ps.getName(); + } + return "N/A"; + } + + + public boolean isConfiguredBy(Configuration configuration) { + // TODO implement this + return true; + } + + public static boolean isConfigured(Class type) { + if (type.getAnnotation(DefaultAreas.class) != null) { + return true; + } + // if no class level annotation is there we might have field level annotations only + for (Field field : type.getDeclaredFields()) { + if (field.isAnnotationPresent(ConfiguredProperties.class)) { + return true; + } + } + // if no class level annotation is there we might have method level annotations only + for (Method method : type.getDeclaredMethods()) { + if (method.isAnnotationPresent(ConfiguredProperties.class)) { + return true; + } + } + for (Field field : type.getDeclaredFields()) { + if (field.isAnnotationPresent(ConfiguredProperty.class)) { + return true; + } + } + // if no class level annotation is there we might have method level annotations only + for (Method method : type.getDeclaredMethods()) { + if (method.isAnnotationPresent(ConfiguredProperty.class)) { + return true; + } + } + return false; + } + + public Class getType() { + return this.type; + } +}