You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tamaya.apache.org by an...@apache.org on 2018/12/15 23:19:24 UTC

[incubator-tamaya] 01/05: TAMAYA-372: Clarified default metadata format.

This is an automated email from the ASF dual-hosted git repository.

anatole pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-tamaya.git

commit 6ab216df0ec6081cd35c1847048b6dab93d1b3bf
Author: Anatole Tresch <at...@gmail.com>
AuthorDate: Tue Dec 11 11:00:37 2018 +0100

    TAMAYA-372: Clarified default metadata format.
---
 .../apache/tamaya/spi/ConfigurationBuilder.java    |   7 +-
 .../apache/tamaya/spi/ConfigurationContext.java    |   7 +-
 .../org/apache/tamaya/spi/ConversionContext.java   |  23 ++--
 .../java/org/apache/tamaya/spi/FilterContext.java  |  41 ++++---
 .../java/org/apache/tamaya/spi/ObjectValue.java    |   4 +-
 .../java/org/apache/tamaya/spi/PropertyValue.java  |   9 +-
 .../java/org/apache/tamaya/ConfigurationTest.java  |  69 +++++++++++-
 .../java/org/apache/tamaya/InvocationRecorder.java |  76 +++++++++++++
 .../java/org/apache/tamaya/TestConfiguration.java  |  25 +++--
 .../apache/tamaya/TestConfigurationProvider.java   |   4 +-
 .../apache/tamaya/spi/ConversionContextTest.java   |  54 +++++++++
 .../org/apache/tamaya/spi/FilterContextTest.java   |  10 ++
 .../org/apache/tamaya/spi/PropertyValueTest.java   | 125 ++++++++++++++++++++-
 .../spisupport/DefaultConfigurationBuilder.java    |   8 +-
 .../spisupport/DefaultConfigurationContext.java    |   4 +-
 .../tamaya/spisupport/DefaultMetaDataProvider.java |  86 ++++++++++----
 .../apache/tamaya/spisupport/MetadataProvider.java |  27 +++--
 .../tamaya/spisupport/PropertyFiltering.java       |   3 +-
 .../spisupport/MockedConfigurationContext.java     |   2 +-
 19 files changed, 491 insertions(+), 93 deletions(-)

diff --git a/code/api/src/main/java/org/apache/tamaya/spi/ConfigurationBuilder.java b/code/api/src/main/java/org/apache/tamaya/spi/ConfigurationBuilder.java
index b4d67ce..0e2d95f 100644
--- a/code/api/src/main/java/org/apache/tamaya/spi/ConfigurationBuilder.java
+++ b/code/api/src/main/java/org/apache/tamaya/spi/ConfigurationBuilder.java
@@ -105,18 +105,21 @@ public interface ConfigurationBuilder {
 
     /**
      * Adds (overrides existing value) the given sources as property sources.
+     *
+     * @param property the property key, not null,
      * @param key the key, not null.
      * @param value the value, not null.
      * @return the current configuration builder.
      */
-    ConfigurationBuilder setMeta(String key, String value);
+    ConfigurationBuilder setMeta(String property, String key, String value);
 
     /**
      * Adds (overrides existing value with same same keys) the given sources as property sources.
+     * @param property the property key, not null,
      * @param metaData the metadata, not null.
      * @return the current configuration builder.
      */
-    ConfigurationBuilder setMeta(Map<String, String> metaData);
+    ConfigurationBuilder setMeta(String property, Map<String, String> metaData);
 
     /**
      * This method can be used for programmatically adding {@link PropertySource}s.
diff --git a/code/api/src/main/java/org/apache/tamaya/spi/ConfigurationContext.java b/code/api/src/main/java/org/apache/tamaya/spi/ConfigurationContext.java
index 86c3831..e762576 100644
--- a/code/api/src/main/java/org/apache/tamaya/spi/ConfigurationContext.java
+++ b/code/api/src/main/java/org/apache/tamaya/spi/ConfigurationContext.java
@@ -34,9 +34,10 @@ public interface ConfigurationContext {
 
     /**
      * Get the metadata evaluated for this configuration.
-     * @return the metadata accessor, never null.
+     * @param key the property key, not null.
+     * @return the metadata fpr this key, never null.
      */
-    Map<String,String> getMetaData();
+    Map<String,String> getMetaData(String key);
 
     /**
      * Access the underlying {@link ServiceContext}.
@@ -149,7 +150,7 @@ public interface ConfigurationContext {
      */
     ConfigurationContext EMPTY = new ConfigurationContext() {
         @Override
-        public Map<String,String> getMetaData() {
+        public Map<String,String> getMetaData(String key) {
             return Collections.emptyMap();
         }
 
diff --git a/code/api/src/main/java/org/apache/tamaya/spi/ConversionContext.java b/code/api/src/main/java/org/apache/tamaya/spi/ConversionContext.java
index ff321dc..cb4ae32 100644
--- a/code/api/src/main/java/org/apache/tamaya/spi/ConversionContext.java
+++ b/code/api/src/main/java/org/apache/tamaya/spi/ConversionContext.java
@@ -99,20 +99,18 @@ public class ConversionContext {
 
 
     /**
-     * Evaluate the current metadata from the given values. Later values hereby are more significant.
+     * Evaluate the metadata for the current target key from the given values. Later values hereby are more significant.
      * @return the evaluated meta data map.
      */
     public Map<String, String> getMeta() {
         Map<String, String> metaMap = new HashMap<>();
-        if(values.size()>0){
-            String baseKey = values.get(0).getQualifiedKey()+".";
-
-            values.forEach(val -> this.getConfiguration().getContext().getMetaData().entrySet().forEach(
-                    en -> {
-                        if(en.getKey().startsWith(baseKey)) {
-                            metaMap.put(en.getKey().substring(baseKey.length()), en.getValue());
-                        }
-                    }));
+        if(configuration!=null){
+            metaMap.putAll(configuration.getContext().getMetaData(key));
+        }
+        for(PropertyValue val:values){
+            if(key.equals(val.getQualifiedKey())){
+                metaMap.putAll(val.getMeta());
+            }
         }
         return metaMap;
     }
@@ -161,7 +159,10 @@ public class ConversionContext {
      */
     @Deprecated
     public ConfigurationContext getConfigurationContext() {
-        return getConfiguration().getContext();
+        if(configuration!=null) {
+            return configuration.getContext();
+        }
+        return ConfigurationContext.EMPTY;
     }
 
     /**
diff --git a/code/api/src/main/java/org/apache/tamaya/spi/FilterContext.java b/code/api/src/main/java/org/apache/tamaya/spi/FilterContext.java
index e120744..cdf8652 100644
--- a/code/api/src/main/java/org/apache/tamaya/spi/FilterContext.java
+++ b/code/api/src/main/java/org/apache/tamaya/spi/FilterContext.java
@@ -21,18 +21,17 @@ package org.apache.tamaya.spi;
 import org.apache.tamaya.Configuration;
 
 import java.util.*;
-import java.util.function.Consumer;
 
 /**
- * A filter context containing all the required values for implementing filtering.
+ * A filter configurationContext containing all the required values for implementing filtering.
  *
  * @see PropertyFilter
  */
 public class FilterContext {
     /** The key. */
     private final List<PropertyValue> values;
-    /** The current context. */
-    private final ConfigurationContext context;
+    /** The current configurationContext. */
+    private final ConfigurationContext configurationContext;
     @Experimental
     private Map<String, PropertyValue> configEntries = new HashMap<>();
     @Experimental
@@ -45,17 +44,17 @@ public class FilterContext {
      *
      * @param value the createValue under evaluation, not {@code null}.
      * @param configEntries the raw configuration data available in the
-     *                      current evaluation context, not {@code null}.
-     * @param context the current context, not {@code null}.
+     *                      current evaluation configurationContext, not {@code null}.
+     * @param configurationContext the current configurationContext, not {@code null}.
      */
-    public FilterContext(PropertyValue value, Map<String,PropertyValue> configEntries, ConfigurationContext context) {
+    public FilterContext(PropertyValue value, Map<String,PropertyValue> configEntries, ConfigurationContext configurationContext) {
         Objects.requireNonNull(value, "Value must not be null.");
         Objects.requireNonNull(configEntries, "Initial configuration entries must be not null.");
-        Objects.requireNonNull(context, "Context must be not null.");
+        Objects.requireNonNull(configurationContext, "Context must be not null.");
 
         this.singlePropertyScoped = false;
         this.values = Collections.singletonList(Objects.requireNonNull(value));
-        this.context = Objects.requireNonNull(context);
+        this.configurationContext = Objects.requireNonNull(configurationContext);
         this.configEntries.putAll(configEntries);
         this.configEntries = Collections.unmodifiableMap(this.configEntries);
     }
@@ -64,14 +63,14 @@ public class FilterContext {
      * Creates a new FilterContext, for filtering of a single createValue access
      * using {@link Configuration#getProperties()}.
      * @param value the createValue under evaluation, not {@code null}.
-     * @param context the current context, not {@code null}.
+     * @param configurationContext the current configurationContext, not {@code null}.
      */
-    public FilterContext(PropertyValue value, ConfigurationContext context) {
+    public FilterContext(PropertyValue value, ConfigurationContext configurationContext) {
         Objects.requireNonNull(value, "Value must not be null.");
-        Objects.requireNonNull(context, "Context must be not null.");
+        Objects.requireNonNull(configurationContext, "Context must be not null.");
 
         this.singlePropertyScoped = true;
-        this.context = Objects.requireNonNull(context);
+        this.configurationContext = Objects.requireNonNull(configurationContext);
         this.values = Collections.singletonList(Objects.requireNonNull(value));
         this.configEntries = Collections.unmodifiableMap(this.configEntries);
     }
@@ -80,24 +79,24 @@ public class FilterContext {
      * Creates a new FilterContext, for filtering of a single createValue access
      * using {@link Configuration#getProperties()}.
      * @param values the createValue under evaluation, not {@code null}.
-     * @param context the current context, not {@code null}.
+     * @param configurationContext the current configurationContext, not {@code null}.
      */
-    public FilterContext(List<PropertyValue> values, ConfigurationContext context) {
+    public FilterContext(List<PropertyValue> values, ConfigurationContext configurationContext) {
         Objects.requireNonNull(values, "Value must not be null.");
-        Objects.requireNonNull(context, "Context must be not null.");
+        Objects.requireNonNull(configurationContext, "Context must be not null.");
 
         this.singlePropertyScoped = true;
-        this.context = Objects.requireNonNull(context);
+        this.configurationContext = Objects.requireNonNull(configurationContext);
         this.values = Collections.unmodifiableList(new ArrayList<>(values));
         this.configEntries = Collections.unmodifiableMap(this.configEntries);
     }
 
     /**
-     * Get the current context.
-     * @return the current context, not {@code null}.
+     * Get the current configurationContext.
+     * @return the current configurationContext, not {@code null}.
      */
-    public ConfigurationContext current(){
-        return context;
+    public ConfigurationContext getConfigurationContext(){
+        return configurationContext;
     }
 
     /**
diff --git a/code/api/src/main/java/org/apache/tamaya/spi/ObjectValue.java b/code/api/src/main/java/org/apache/tamaya/spi/ObjectValue.java
index f7f6330..7d09eee 100644
--- a/code/api/src/main/java/org/apache/tamaya/spi/ObjectValue.java
+++ b/code/api/src/main/java/org/apache/tamaya/spi/ObjectValue.java
@@ -56,7 +56,7 @@ public final class ObjectValue extends PropertyValue{
      * @return the createValue type, never null.
      */
     public ValueType getValueType() {
-        return ValueType.OBJECT;
+        return ValueType.MAP;
     }
 
     /**
@@ -305,7 +305,7 @@ public final class ObjectValue extends PropertyValue{
 
     @Override
     public String toString() {
-        return "PropertyValue[OBJECT]{" +
+        return "PropertyValue[MAP]{" +
                 '\'' +getQualifiedKey() + '\'' +
                 (getValue()!=null?", createValue='" + getValue() + '\'':"") +
                 ", size='" + getSize() + '\'' +
diff --git a/code/api/src/main/java/org/apache/tamaya/spi/PropertyValue.java b/code/api/src/main/java/org/apache/tamaya/spi/PropertyValue.java
index 9381f5e..048e4b2 100644
--- a/code/api/src/main/java/org/apache/tamaya/spi/PropertyValue.java
+++ b/code/api/src/main/java/org/apache/tamaya/spi/PropertyValue.java
@@ -53,7 +53,7 @@ public class PropertyValue implements Serializable, Iterable<PropertyValue>{
      */
     public enum ValueType{
         /** A multi valued property value, which contains named child properties. */
-        OBJECT,
+        MAP,
         /** A multi valued property value, which contains unnamed child properties. */
         ARRAY,
         /** A simple value property. */
@@ -74,7 +74,7 @@ public class PropertyValue implements Serializable, Iterable<PropertyValue>{
         Objects.requireNonNull(key, "Key must be given.");
         Objects.requireNonNull(source, "Source must be given");
 
-        return new PropertyValueBuilder(key, source);
+        return new PropertyValueBuilder(key, null).setSource(source);
     }
 
     /**
@@ -113,7 +113,7 @@ public class PropertyValue implements Serializable, Iterable<PropertyValue>{
     }
 
     /**
-     * Creates a new createValue of type {@link ValueType#OBJECT}.
+     * Creates a new createValue of type {@link ValueType#MAP}.
      * @param key the key, not {@code null}.
      * @return a new createValue instance.
      */
@@ -569,8 +569,9 @@ public class PropertyValue implements Serializable, Iterable<PropertyValue>{
      * Sets the new parent, used iternally when converting between value types.
      * @param parent the parent value.
      */
-    protected final void setParent(PropertyValue parent){
+    protected PropertyValue setParent(PropertyValue parent){
         this.parent = parent;
+        return this;
     }
 
 
diff --git a/code/api/src/test/java/org/apache/tamaya/ConfigurationTest.java b/code/api/src/test/java/org/apache/tamaya/ConfigurationTest.java
index 58811b4..d950c01 100644
--- a/code/api/src/test/java/org/apache/tamaya/ConfigurationTest.java
+++ b/code/api/src/test/java/org/apache/tamaya/ConfigurationTest.java
@@ -19,9 +19,12 @@
 package org.apache.tamaya;
 
 import org.apache.tamaya.spi.ConfigurationBuilder;
+import org.apache.tamaya.spi.ConfigurationContext;
 import org.junit.Test;
 import org.mockito.Mockito;
 
+import java.math.BigDecimal;
+import java.util.Arrays;
 import java.util.function.Function;
 import java.util.function.UnaryOperator;
 
@@ -36,6 +39,26 @@ import static org.assertj.core.api.Assertions.*;
 public class ConfigurationTest {
 
     @Test
+    public void test_current() throws Exception {
+        assertThat(Configuration.current()).isNotNull();
+    }
+
+    @Test
+    public void test_current_classloader() throws Exception {
+        assertThat(Configuration.current(ClassLoader.getSystemClassLoader())).isNotNull();
+    }
+
+    @Test
+    public void test_release() throws Exception {
+        Configuration c1 = Configuration.current();
+        Configuration c2 = Configuration.current();
+        Configuration.releaseConfiguration(c2.getContext().getServiceContext().getClassLoader());
+        Configuration c3 = Configuration.current();
+        assertThat(c1).isSameAs(c2);
+        assertThat(c2).isNotSameAs(c3);
+    }
+
+    @Test
     public void testget() throws Exception {
         assertThat(Boolean.TRUE).isEqualTo(Configuration.current().get("booleanTrue", Boolean.class));
         assertThat(Boolean.FALSE).isEqualTo(Configuration.current().get("booleanFalse", Boolean.class));
@@ -48,10 +71,33 @@ public class ConfigurationTest {
     }
 
     @Test
+    public void testget_Iterable() throws Exception {
+        assertThat(Configuration.current().get(Arrays.asList("String","foo"))).isEqualTo("aStringValue");
+        assertThat(Configuration.current().get(Arrays.asList("foo", "bar", "String"))).isEqualTo("aStringValue");
+    }
+
+    @Test
+    public void testget_Iterable_default() throws Exception {
+        assertThat("foo").isEqualTo(Configuration.current().getOrDefault(Arrays.asList("adasd","safsada"), "foo"));
+        assertThat("aStringValue").isEqualTo(Configuration.current().getOrDefault(Arrays.asList("foo1", "bar1", "String"), "foo"));
+    }
+
+    @Test
+    public void testget_Iterable_default_typed() throws Exception {
+        assertThat(25).isEqualTo(Configuration.current().getOrDefault(Arrays.asList("adasd","safsada"),Integer.class,25));
+        assertThat(BigDecimal.ZERO).isEqualTo(Configuration.current().getOrDefault(Arrays.asList("foo1", "bar1", "2.0"), BigDecimal.class, BigDecimal.ZERO));
+    }
+
+    @Test
+    public void testget_Iterable_with_Type() throws Exception {
+        assertThat(Boolean.TRUE).isEqualTo(Configuration.current().get(Arrays.asList("booleanTrue","booleanFalse"), Boolean.class));
+        assertThat(Boolean.TRUE).isEqualTo(Configuration.current().get(Arrays.asList("foo1", "bar1", "booleanTrue"), Boolean.class));
+    }
+
+    @Test
     public void testGetBoolean() throws Exception {
         assertThat(Configuration.current().get("booleanTrue", Boolean.class)).isTrue();
         assertThat(Configuration.current().get("booleanFalse", Boolean.class)).isFalse();
-        assertThat(Configuration.current().get("foorBar", Boolean.class)).isFalse();
     }
 
     @Test
@@ -133,4 +179,25 @@ public class ConfigurationTest {
         ConfigurationBuilder result = Configuration.createConfigurationBuilder();
         assertThat(result instanceof ConfigurationBuilder).isTrue();
     }
+
+    @Test
+    public void testEmpty() {
+        Configuration c = Configuration.EMPTY;
+        assertThat(c).isNotNull();
+        assertThat(c.get("foo")).isNull();
+        assertThat(c.get("foo", Boolean.class)).isNull();
+        assertThat(c.get(Arrays.asList("foo", "bar"))).isNull();
+        assertThat(c.getOrDefault("foo", "")).isEqualTo("");
+        assertThat(c.getOrDefault("foo", Integer.class, 234)).isEqualTo(234);
+        assertThat(c.getOrDefault(Arrays.asList("foo", "bar"), "default")).isEqualTo("default");
+        assertThat(c.getOrDefault(Arrays.asList("foo", "bar"), Integer.class, 234)).isEqualTo(234);
+        assertThat(c.get(Arrays.asList("foo", "bar"), Integer.class)).isNull();
+        assertThat(c.getContext()).isEqualTo(ConfigurationContext.EMPTY);
+        assertThat(c.getOptional("kjhkjh")).isNotNull();
+        assertThat(c.getOptional("kjhkjh")).isNotPresent();
+        assertThat(c.getOptional("kjhkjh", Integer.class)).isNotNull();
+        assertThat(c.getOptional("kjhkjh", Integer.class)).isNotPresent();
+        assertThat(c.get("foo")).isNull();
+        assertThat(c.get("foo", Boolean.class)).isNull();
+    }
 }
diff --git a/code/api/src/test/java/org/apache/tamaya/InvocationRecorder.java b/code/api/src/test/java/org/apache/tamaya/InvocationRecorder.java
new file mode 100644
index 0000000..a3ebdc7
--- /dev/null
+++ b/code/api/src/test/java/org/apache/tamaya/InvocationRecorder.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;
+
+
+import org.junit.Assert;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class InvocationRecorder {
+
+    private List<Invocation> invocations = new ArrayList<>();
+
+    private Object record(Object instance, Method method, Object[] args) throws Throwable {
+        Invocation invocation = new Invocation(method.getName(), args);
+        this.invocations.add(invocation);
+        return method.invoke(instance, args);
+    }
+
+    public <T> T createProxy(Object instance, Class<T>... types) {
+        return (T) Proxy.newProxyInstance(
+                getClass().getClassLoader(), types,
+                (proxy,method,params) -> this.record(instance, method, params));
+    }
+
+    public void recordMethodCall(Object... params) {
+        Exception e = new Exception();
+        String methodName = e.getStackTrace()[1].getMethodName();
+        invocations.add(new Invocation(methodName, params));
+    }
+
+    public static final class Invocation{
+        public String methodName;
+        public Object[] params;
+
+        public Invocation(String methodName, Object[] params) {
+            this.methodName = methodName;
+            this.params = params;
+        }
+    }
+
+    public List<Invocation> getInvocations(){
+        return invocations;
+    }
+
+    public void assertInvocation(String method, Object... params){
+        for(Invocation invocation:invocations){
+            if(invocation.methodName.equals(method)){
+                if(Arrays.equals(invocation.params, params)){
+                    return;
+                }
+            }
+        }
+        Assert.fail("No such invocation: "+method + Arrays.toString(params));
+    }
+}
diff --git a/code/api/src/test/java/org/apache/tamaya/TestConfiguration.java b/code/api/src/test/java/org/apache/tamaya/TestConfiguration.java
index cd6eeaf..d06753a 100644
--- a/code/api/src/test/java/org/apache/tamaya/TestConfiguration.java
+++ b/code/api/src/test/java/org/apache/tamaya/TestConfiguration.java
@@ -49,9 +49,7 @@ public class TestConfiguration implements Configuration {
         VALUES.put("String", "aStringValue");
     }
 
-    @SuppressWarnings("unchecked")
-    @Override
-    public <T> T get(String key, TypeLiteral<T> type) {
+    private <T> T getInternal(String key, TypeLiteral<T> type) {
         if (type.getRawType().equals(Long.class)) {
             return (T) VALUES.get(key);
         } else if (type.getRawType().equals(Integer.class)) {
@@ -67,18 +65,31 @@ public class TestConfiguration implements Configuration {
         } else if (type.getRawType().equals(Boolean.class)) {
             if ("booleanTrue".equals(key)) {
                 return (T) Boolean.TRUE;
-            } else {
+            } else if ("booleanFalse".equals(key)) {
                 return (T) Boolean.FALSE;
             }
         } else if (type.getRawType().equals(String.class)) {
-            return (T) VALUES.get(key);
+            Object value = VALUES.get(key);
+            if(value!=null){
+                return (T)String.valueOf(value);
+            }
+        }
+        return null;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T> T get(String key, TypeLiteral<T> type) {
+        T t = getInternal(key, type);
+        if(t==null) {
+            throw new ConfigException("No such property: " + key);
         }
-        throw new ConfigException("No such property: " + key);
+        return t;
     }
 
     @Override
     public <T> T getOrDefault(String key, TypeLiteral<T> type, T defaultValue) {
-        T val = get(key, type);
+        T val = getInternal(key, type);
         if (val == null) {
             return defaultValue;
         }
diff --git a/code/api/src/test/java/org/apache/tamaya/TestConfigurationProvider.java b/code/api/src/test/java/org/apache/tamaya/TestConfigurationProvider.java
index e5622e4..fae3c53 100644
--- a/code/api/src/test/java/org/apache/tamaya/TestConfigurationProvider.java
+++ b/code/api/src/test/java/org/apache/tamaya/TestConfigurationProvider.java
@@ -57,6 +57,8 @@ public class TestConfigurationProvider implements ConfigurationProviderSpi {
 
     @Override
     public Configuration releaseConfiguration(ClassLoader classloader) {
-        return null;
+        Configuration prev = config;
+        config = new TestConfiguration();
+        return prev;
     }
 }
diff --git a/code/api/src/test/java/org/apache/tamaya/spi/ConversionContextTest.java b/code/api/src/test/java/org/apache/tamaya/spi/ConversionContextTest.java
index 620b2b6..7e6110e 100644
--- a/code/api/src/test/java/org/apache/tamaya/spi/ConversionContextTest.java
+++ b/code/api/src/test/java/org/apache/tamaya/spi/ConversionContextTest.java
@@ -26,9 +26,11 @@ import org.junit.Test;
 
 import java.net.InetAddress;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 import static org.assertj.core.api.Assertions.*;
+import static org.junit.Assert.*;
 
 /**
  * Tests for {@link ConversionContext}, created by atsticks on 20.08.16.
@@ -97,6 +99,58 @@ public class ConversionContextTest {
         assertThat(ctx.toString()).isEqualTo("ConversionContext{configuration=null, key='toString', targetType=TypeLiteral{type=interface java.util.List}, annotatedElement=null, supportedFormats=[0.0.0.0/nnn (MyConverter), x.x.x.x/yyy (MyConverter)]}");
     }
 
+    @Test
+    public void testGetSetValues_Ellipse(){
+        ConversionContext ctx = new ConversionContext.Builder("toString", TypeLiteral.of(List.class))
+                .addSupportedFormats(MyConverter.class, "0.0.0.0/nnn", "x.x.x.x/yyy")
+                .setValues(PropertyValue.createValue("test", "value")).build();
+        assertNotNull(ctx.getValues());
+        assertEquals(ctx.getValues().size(), 1);
+        assertEquals("value", ctx.getValues().get(0).getValue());
+        assertEquals("test", ctx.getValues().get(0).getKey());
+    }
+
+    @Test
+    public void testGetSetValues_List(){
+        ConversionContext ctx = new ConversionContext.Builder("toString", TypeLiteral.of(List.class))
+                .addSupportedFormats(MyConverter.class, "0.0.0.0/nnn", "x.x.x.x/yyy")
+                .setValues(Collections.singletonList(PropertyValue.createValue("test", "value"))).build();
+        assertNotNull(ctx.getValues());
+        assertEquals(ctx.getValues().size(), 1);
+        assertEquals("value", ctx.getValues().get(0).getValue());
+        assertEquals("test", ctx.getValues().get(0).getKey());
+    }
+
+    @Test
+    public void testGetConfigurationContext(){
+        ConversionContext ctx = new ConversionContext.Builder("toString", TypeLiteral.of(List.class))
+                .addSupportedFormats(MyConverter.class, "0.0.0.0/nnn", "x.x.x.x/yyy")
+                .setValues(PropertyValue.createValue("test", "value")).build();
+        assertNotNull(ctx.getConfigurationContext());
+    }
+
+    @Test
+    public void testGetMeta(){
+        ConversionContext ctx = new ConversionContext.Builder("test", TypeLiteral.of(List.class))
+                .addSupportedFormats(MyConverter.class, "0.0.0.0/nnn", "x.x.x.x/yyy")
+                .setValues(PropertyValue.createValue("test", "value")
+                .setMeta("meta1", "val1").setMeta("meta2", "val2")).build();
+        assertNotNull(ctx.getMeta());
+        assertFalse(ctx.getMeta().isEmpty());
+        assertEquals(2, ctx.getMeta().size());
+    }
+
+    @Test
+    public void testBuilderToString() {
+        ConversionContext.Builder b = new ConversionContext.Builder("toString", TypeLiteral.of(List.class))
+                .addSupportedFormats(MyConverter.class, "0.0.0.0/nnn", "x.x.x.x/yyy");
+        assertNotNull(b.toString());
+        assertTrue(b.toString().contains("targetType=TypeLiteral{type=interface java.util.List}"));
+        assertTrue(b.toString().contains("supportedFormats=[0.0.0.0/nnn (MyConverter), x.x.x.x/yyy (MyConverter)]"));
+        assertTrue(b.toString().contains("annotatedElement"));
+        assertTrue(b.toString().contains("key='toString'"));
+        assertTrue(b.toString().contains("Builder"));
+    }
 
     private static final AnnotatedElement MyAnnotatedElement = new AnnotatedElement() {
         @Override
diff --git a/code/api/src/test/java/org/apache/tamaya/spi/FilterContextTest.java b/code/api/src/test/java/org/apache/tamaya/spi/FilterContextTest.java
index 1b74f0b..24fed66 100644
--- a/code/api/src/test/java/org/apache/tamaya/spi/FilterContextTest.java
+++ b/code/api/src/test/java/org/apache/tamaya/spi/FilterContextTest.java
@@ -37,6 +37,16 @@ import static org.junit.Assert.assertNotNull;
  */
 public class FilterContextTest {
 
+    @Test
+    public void constructorWithContext() {
+        PropertyValue val = new PropertyValue(null, "");
+        FilterContext ctx = new FilterContext(val, ConfigurationContext.EMPTY);
+        assertEquals(val, ctx.getProperty());
+        assertEquals(ConfigurationContext.EMPTY, ctx.getConfigurationContext());
+        assertNotNull(ctx.getConfigEntries());
+        assertEquals(1, ctx.getAllValues().size());
+    }
+
 //    @Test
 //    public void setNullContext() {
 //        FilterContext.set(null);
diff --git a/code/api/src/test/java/org/apache/tamaya/spi/PropertyValueTest.java b/code/api/src/test/java/org/apache/tamaya/spi/PropertyValueTest.java
index 035c458..6317714 100644
--- a/code/api/src/test/java/org/apache/tamaya/spi/PropertyValueTest.java
+++ b/code/api/src/test/java/org/apache/tamaya/spi/PropertyValueTest.java
@@ -43,6 +43,14 @@ public class PropertyValueTest {
     }
 
     @Test
+    public void builder() throws Exception {
+        PropertyValueBuilder b = PropertyValue.builder("a", "b");
+        assertNotNull(b);
+        assertEquals("a", b.key);
+        assertEquals("b", b.source);
+    }
+
+    @Test
     public void testOf(){
         assertThat(PropertyValue.of("k", "v", "testGetKey")).isNotNull();
     }
@@ -116,6 +124,41 @@ public class PropertyValueTest {
     }
 
     @Test
+    public void testMap() throws Exception {
+        Map<String, String> map = new HashMap<>();
+        map.put("a", "b");
+        map.put("b", "c");
+        Map<String, PropertyValue> result = PropertyValue.map(map, "source");
+        assertNotNull(result);
+        assertEquals(map.size(), result.size());
+       for(PropertyValue pv:result.values()){
+           assertEquals("source", pv.getMetaEntry("source"));
+       }
+        assertEquals("b", map.get("a"));
+        assertEquals("c", map.get("b"));
+    }
+
+    @Test
+    public void testMap_WithMeta() throws Exception {
+        Map<String, String> map = new HashMap<>();
+        map.put("a", "b");
+        map.put("b", "c");
+        Map<String, String> meta = new HashMap<>();
+        meta.put("m1", "m1v");
+        meta.put("m2", "m2v");
+        Map<String, PropertyValue> result = PropertyValue.map(map, "source", meta);
+        assertNotNull(result);
+        assertEquals(map.size(), result.size());
+        for(PropertyValue pv:result.values()){
+            assertEquals("source", pv.getMetaEntry("source"));
+            assertEquals("m1v", pv.getMeta("m1"));
+            assertEquals("m2v", pv.getMeta("m2"));
+        }
+        assertEquals("b", map.get("a"));
+        assertEquals("c", map.get("b"));
+    }
+
+    @Test
     public void testHashCode(){
         assertThat(PropertyValue.of("k", "v", "testGetKey").hashCode()).isEqualTo(PropertyValue.of("k", "v", "testGetKey").hashCode());
         assertThat(PropertyValue.of("k", "v", "testGetKey").hashCode()).isNotSameAs(PropertyValue.of("k1", "v", "testGetKey").hashCode());
@@ -260,6 +303,39 @@ public class PropertyValueTest {
     }
 
     @Test
+    public void isImmutable() {
+        PropertyValue n = PropertyValue.createValue("", "");
+        assertFalse(n.isImmutable());
+        n.immutable();
+        assertTrue(n.isImmutable());
+        assertFalse(n.mutable().isImmutable());
+    }
+
+    @Test
+    public void isRoot() {
+        PropertyValue n = PropertyValue.createValue("", "");
+        assertTrue(n.isRoot());
+        n = PropertyValue.createValue("", "").setParent(n);
+        assertFalse(n.isRoot());
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void checkImmutableChangeThrowsExceotion() {
+        PropertyValue n = PropertyValue.createValue("", "");
+        n.immutable();
+        n.setValue("jhgjg");
+    }
+
+    @Test
+    public void checkMutable() {
+        PropertyValue n = PropertyValue.createValue("", "");
+        n.immutable();
+        n = n.mutable();
+        n.setValue("jhgjg");
+        assertEquals("jhgjg", n.getValue());
+    }
+
+    @Test
     public void getParent() {
         ObjectValue n = PropertyValue.createObject("");
         assertNull(n.getParent());
@@ -268,6 +344,53 @@ public class PropertyValueTest {
         assertNotNull(n.getField("b").getParent());
     }
 
+    @Test
+    public void size(){
+        PropertyValue n = PropertyValue.createValue("key", "");
+        assertEquals(0, n.getSize());
+        assertFalse(n.iterator().hasNext());
+    }
+
+    @Test
+    public void setValue() {
+        PropertyValue n = PropertyValue.createValue("key", "");
+        assertEquals("", n.getValue());
+        n.setValue("jhgjg");
+        assertEquals("jhgjg", n.getValue());
+    }
+
+    @Test
+    public void setKey() {
+        PropertyValue n = PropertyValue.createValue("key", "");
+        assertEquals("key", n.getKey());
+        n.setKey("jhgjg");
+        assertEquals("jhgjg", n.getKey());
+    }
+
+    @Test
+    public void toBuilder() {
+        PropertyValue n = PropertyValue.createValue("key", "");
+        assertNotNull(n.toBuilder());
+    }
+
+    @Test
+    public void toPropertyValue() {
+        PropertyValue n = PropertyValue.createValue("key", "");
+        assertTrue(n == n.toPropertyValue());
+    }
+
+    @Test
+    public void toObjectValue() {
+        PropertyValue n = PropertyValue.createValue("key", "");
+        assertNotNull(n.toObjectValue());
+    }
+
+    @Test
+    public void toListValue() {
+        PropertyValue n = PropertyValue.createValue("key", "");
+        assertNotNull(n.toListValue());
+    }
+
 //    @Test
 //    public void getChildren_Filtered() {
 //        PropertyValue n = PropertyValue.createObject();
@@ -345,7 +468,7 @@ public class PropertyValueTest {
         n.setField("a", "aVal");
         n.setField("b.b2.b3", "b3Val");
         n.setFieldList("c").addValue("cVal1");
-        assertEquals("PropertyValue[OBJECT]{'', size='3'}", n.toString());
+        assertEquals("PropertyValue[MAP]{'', size='3'}", n.toString());
     }
 
 }
\ No newline at end of file
diff --git a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationBuilder.java b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationBuilder.java
index 1276daa..95c4d50 100644
--- a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationBuilder.java
+++ b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationBuilder.java
@@ -122,14 +122,14 @@ public class DefaultConfigurationBuilder implements ConfigurationBuilder {
     }
 
     @Override
-    public ConfigurationBuilder setMeta(String key, String value){
-        this.metaDataProvider.setMeta(key, value);
+    public ConfigurationBuilder setMeta(String property, String key, String value){
+        this.metaDataProvider.setMeta(property, key, value);
         return this;
     }
 
     @Override
-    public ConfigurationBuilder setMeta(Map<String, String> metaData){
-        this.metaDataProvider.setMeta(metaData);
+    public ConfigurationBuilder setMeta(String property, Map<String, String> metaData){
+        this.metaDataProvider.setMeta(property, metaData);
         return this;
     }
 
diff --git a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationContext.java b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationContext.java
index a6d9612..1c58419 100644
--- a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationContext.java
+++ b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultConfigurationContext.java
@@ -103,8 +103,8 @@ public class DefaultConfigurationContext implements ConfigurationContext {
 
 
     @Override
-    public Map<String,String> getMetaData() {
-        return metaDataProvider.getMetaData();
+    public Map<String,String> getMetaData(String key) {
+        return metaDataProvider.getMetaData(key);
     }
 
     @Override
diff --git a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultMetaDataProvider.java b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultMetaDataProvider.java
index cc43360..c1c5be1 100644
--- a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultMetaDataProvider.java
+++ b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/DefaultMetaDataProvider.java
@@ -25,19 +25,21 @@ import org.apache.tamaya.spi.PropertyValue;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Logger;
 
 /**
  * Default metadata provider implementation, which searches for all kind of entries
- * starting with {@code [META]}. All matching key/values are added to the
- * meta data map, hereby reoving the key prefix.
+ * formatted as {@code [(META)key].metaKey=metaValue}. All matching key/values are added to the
+ * meta data map for the given key as {@code metaKey=metaValue} meta entries.
  */
 public class DefaultMetaDataProvider implements MetadataProvider {
 
-    private static final String META_PREFIX = "[META]";
+    private static final Logger LOG = Logger.getLogger(DefaultMetaDataProvider.class.getName());
+    private static final String META_PREFIX = "[(META)";
     private ConfigurationContext context;
-    private Map<String, String> additionalProperties = new ConcurrentHashMap<>();
+    private Map<String, Map<String, String>> additionalProperties = new ConcurrentHashMap<>();
     private AtomicLong lastHash = new AtomicLong();
-    private Map<String, String> propertyCache = new HashMap<>();
+    private Map<String, Map<String, String>> propertyCache = new HashMap<>();
 
     @Override
     public MetadataProvider init(ConfigurationContext context) {
@@ -46,50 +48,88 @@ public class DefaultMetaDataProvider implements MetadataProvider {
     }
 
     @Override
-    public Map<String, String> getMetaData() {
+    public Map<String, String> getMetaData(String property) {
         long configHash = Objects.hash(context.getPropertySources().toArray());
         if(configHash!=lastHash.get()){
             lastHash.set(configHash);
             propertyCache = loadMetaProperties();
         }
-        return propertyCache;
+        Map<String, String> meta = propertyCache.get(property);
+        if(meta==null){
+            return Collections.emptyMap();
+        }
+        return Collections.unmodifiableMap(meta);
     }
 
-    private Map<String, String> loadMetaProperties() {
-        Map<String, String> result = new HashMap<>();
+    private Map<String, Map<String, String> > loadMetaProperties() {
+        Map<String, Map<String, String> > result = new HashMap<>();
         for(PropertySource ps:context.getPropertySources()){
             ps.getProperties().values().forEach(v -> {
-                loadMetaData(v, result);
+                Map<String, String> meta = result.computeIfAbsent(v.getKey(), k -> new HashMap<>());
+                meta.putAll(v.getMeta());
+                if(v.getQualifiedKey().toUpperCase(Locale.ENGLISH).startsWith(META_PREFIX)){
+                    loadExplicitMetadata(v);
+                }
             });
         }
-        result.putAll(additionalProperties);
+        // Override with manual properties
+        for(Map.Entry<String,Map<String, String>> en: additionalProperties.entrySet()) {
+            Map<String, String> meta = result.get(en.getKey());
+            meta.putAll(en.getValue());
+        }
         return Collections.unmodifiableMap(result);
     }
 
     /**
-     * Iterates all values and it's children and adds all meta-entries found.
-     * @param value the starting value.
-     * @param result the result map to add/override values found.
+     * Iterates all values and it's children and adds all meta-entries found in the configuration
+     * (entries starting with {@code [(META)}).
      */
-    private void loadMetaData(PropertyValue value, Map<String, String> result) {
+    private void loadExplicitMetadata(PropertyValue value) {
         String key = value.getQualifiedKey();
-        if(key.toUpperCase(Locale.ENGLISH).startsWith(META_PREFIX)){
-            if(value.getValue()!=null){
-                result.put(key.substring(META_PREFIX.length()), value.getValue());
+        if(value.getValue()!=null){
+            String[] keyValue = getMetaKeys(key);
+            if(keyValue==null){
+                LOG.warning("Encountered invalid META-ENTRY: " + key);
+            }else {
+                Map<String, String> meta = additionalProperties.computeIfAbsent(keyValue[0], k -> new HashMap<>());
+                meta.put(keyValue[1], value.getValue());
             }
-            value.iterator().forEachRemaining(v -> loadMetaData(v, result));
         }
     }
 
+    private String[] getMetaKeys(String fullKey) {
+        String strippedKey = fullKey.substring(META_PREFIX.length());
+        int index = strippedKey.lastIndexOf(']');
+        if(index<0){
+            // invalid meta key
+            return null;
+        }
+        String[] result = new String[2];
+        result[0] = strippedKey.substring(0,index);
+        result[1] = strippedKey.substring(index+1);
+        if(result[1].startsWith(".")){
+            result[1] = result[1].substring(1);
+        }
+        return result;
+    }
+
+    @Override
+    public MetadataProvider setMeta(String property, String key, String value) {
+        additionalProperties.computeIfAbsent(property, p -> new HashMap<>())
+            .put(key, value);
+        return this;
+    }
+
     @Override
-    public MetadataProvider setMeta(String key, String value) {
-        additionalProperties.put(key, value);
+    public MetadataProvider setMeta(String property, Map<String, String> metaData) {
+        additionalProperties.computeIfAbsent(property, p -> new HashMap<>())
+                .putAll(metaData);
         return this;
     }
 
     @Override
-    public MetadataProvider setMeta(Map<String, String> metaData) {
-        additionalProperties.putAll(metaData);
+    public MetadataProvider reset(String property) {
+        additionalProperties.remove(property);
         return this;
     }
 
diff --git a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/MetadataProvider.java b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/MetadataProvider.java
index b5040fa..9f80dec 100644
--- a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/MetadataProvider.java
+++ b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/MetadataProvider.java
@@ -25,7 +25,7 @@ import java.util.Map;
 /**
  * This interface allows to plugin different metadata mechanism. The default implementation
  * does load metadata information along the same property sources hierarchy as configuration.
- * MetaData entries are identified by a {@code [META]} prefix. Alternate implementations can
+ * MetaData entries are in the format {@code [(META)key].metakey=metavalue}. Alternate implementations can
  * choose whatever is appropriate, including loading metadata from external sources.
  */
 public interface MetadataProvider {
@@ -39,31 +39,42 @@ public interface MetadataProvider {
     MetadataProvider init(ConfigurationContext context);
 
     /**
-     * Access the current metadata for the given configuration context. The MetaData will be
-     * accessible from {@link ConfigurationContext#getMetaData()}. Note that the metadata must not
+     * Access the current metadata for the given configuration context and key. The MetaData will be
+     * accessible from {@link ConfigurationContext#getMetaData(String)}. Note that the metadata must not
      * to be cached by it's consumers, so caching/optimazitation is delegated to this implementation.
+     * @param key the property key, not null.
      * @return the (immutable) metadata of this configuration context.
      */
-    Map<String,String> getMetaData();
+    Map<String,String> getMetaData(String key);
 
     /**
      * Adds additional metadata. This metadata entries typically override all entries
      * from alternate sources.
      *
-     * @param key the key, not null.
-     * @param value the value, not null.
+     * @param property the property key, not null.
+     * @param key the metadata key, not null.
+     * @param value the metadata value, not null.
      * @return this instance, for chaining.
      */
-    MetadataProvider setMeta(String key, String value);
+    MetadataProvider setMeta(String property, String key, String value);
 
     /**
      * Adds additional metadata. This metadata entries typically override all entries
      * from alternate sources.
      *
+     * @param property the property key, not null.
      * @param metaData the metadata to set/replace.
      * @return this instance, for chaining.
      */
-    MetadataProvider setMeta(Map<String, String> metaData);
+    MetadataProvider setMeta(String property, Map<String, String> metaData);
+
+    /**
+     * Resets metadata for a property, which means it reloads metadata based on the given context and
+     *
+     * param property the property key, not null.
+     * @return this instance, for chaining.
+     */
+    MetadataProvider reset(String property);
 
     /**
      * Resets this instance, which means it reloads metadata based on the given context and
diff --git a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyFiltering.java b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyFiltering.java
index 8df6a21..c6943bf 100644
--- a/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyFiltering.java
+++ b/code/spi-support/src/main/java/org/apache/tamaya/spisupport/PropertyFiltering.java
@@ -21,7 +21,6 @@ package org.apache.tamaya.spisupport;
 import org.apache.tamaya.spi.*;
 
 import java.util.*;
-import java.util.logging.Filter;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -103,7 +102,7 @@ public final class PropertyFiltering{
 
         for (int i = 0; i < MAX_FILTER_LOOPS; i++) {
             int changes = 0;
-            for (PropertyFilter filter : context.current().getPropertyFilters()) {
+            for (PropertyFilter filter : context.getConfigurationContext().getPropertyFilters()) {
                 String value = filteredValue!=null?filteredValue.getValue():null;
                 filteredValue = filter.filterProperty(filteredValue, context);
                 String newValue = filteredValue!=null?filteredValue.getValue():null;
diff --git a/code/spi-support/src/test/java/org/apache/tamaya/spisupport/MockedConfigurationContext.java b/code/spi-support/src/test/java/org/apache/tamaya/spisupport/MockedConfigurationContext.java
index 02f1ede..0f941bf 100644
--- a/code/spi-support/src/test/java/org/apache/tamaya/spisupport/MockedConfigurationContext.java
+++ b/code/spi-support/src/test/java/org/apache/tamaya/spisupport/MockedConfigurationContext.java
@@ -40,7 +40,7 @@ public class MockedConfigurationContext implements ConfigurationContext {
     }
 
     @Override
-    public Map<String, String> getMetaData() {
+    public Map<String, String> getMetaData(String key) {
         return metaData;
     }