blob: fa27237ceb6997c583d8194e768245cb05bd609f [file] [log] [blame]
/*
* 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.spisupport;
import org.apache.tamaya.spi.ConfigurationContext;
import org.apache.tamaya.spi.PropertySource;
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
* formatted as {@code [(META)key].metaKey=metaValue}. All matching key/values are added to the
* meta data mapProperties for the given key as {@code metaKey=metaValue} meta entries.
*/
public class DefaultMetaDataProvider implements MetadataProvider {
private static final Logger LOG = Logger.getLogger(DefaultMetaDataProvider.class.getName());
private static final String META_PREFIX = "[(META)";
private ConfigurationContext context;
private Map<String, Map<String, String>> additionalProperties = new ConcurrentHashMap<>();
private AtomicLong lastHash = new AtomicLong();
private Map<String, Map<String, String>> propertyCache = new HashMap<>();
@Override
public MetadataProvider init(ConfigurationContext context) {
this.context = Objects.requireNonNull(context);
return this;
}
@Override
public Map<String, String> getMetaData(String property) {
long configHash = Objects.hash(context.getPropertySources().toArray());
if(configHash!=lastHash.get()){
lastHash.set(configHash);
propertyCache = loadMetaProperties();
}
Map<String, String> meta = propertyCache.get(property);
if(meta==null){
return Collections.emptyMap();
}
return Collections.unmodifiableMap(meta);
}
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 -> {
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);
}
});
}
// Override with manual properties
for(Map.Entry<String,Map<String, String>> en: additionalProperties.entrySet()) {
Map<String, String> meta = result.get(en.getKey());
if(meta==null){
result.put(en.getKey(), new HashMap<>(en.getValue()));
}else {
meta.putAll(en.getValue());
}
}
return Collections.unmodifiableMap(result);
}
/**
* Iterates all values and it's children and adds all meta-entries found in the configuration
* (entries starting with {@code [(META)}).
*/
private void loadExplicitMetadata(PropertyValue value) {
String key = value.getQualifiedKey();
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());
}
}
}
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) {
Objects.requireNonNull(property, "property null");
Objects.requireNonNull(key, "key null");
Objects.requireNonNull(value, "value null");
additionalProperties.computeIfAbsent(property, p -> new HashMap<>())
.put(key, value);
return this;
}
@Override
public MetadataProvider setMeta(String property, Map<String, String> metaData) {
Objects.requireNonNull(property, "property null");
Objects.requireNonNull(metaData, "metaData null");
additionalProperties.computeIfAbsent(property, p -> new HashMap<>())
.putAll(metaData);
return this;
}
@Override
public MetadataProvider reset(String property) {
Objects.requireNonNull(property, "property null");
additionalProperties.remove(property);
return this;
}
@Override
public MetadataProvider reset() {
additionalProperties.clear();
return this;
}
@Override
public String toString() {
return new StringJoiner(", ", this.getClass().getSimpleName() + "[", "]")
.add("additionalProperties = " + additionalProperties)
.add("context = " + context)
.toString();
}
}