blob: 5632cf20a76adff60a824e786fefb1bf11603d22 [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.ChangeSupport;
import org.apache.tamaya.spi.PropertySource;
import org.apache.tamaya.spi.PropertyValue;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Simple support class for helping with change management on property sources.
*/
public final class PropertySourceChangeSupport {
private static final Logger LOG = Logger.getLogger(PropertySourceChangeSupport.class.getName());
private ChangeSupport changeSupport;
private PropertySource propertySource;
private AtomicLong version = new AtomicLong();
private List<BiConsumer<Set<String>, PropertySource>> listeners = new ArrayList<>();
private int oldHash = 0;
private Map<String, PropertyValue> valueMap;
private long timestamp;
private ScheduledFuture scheduleTask;
private static ScheduledExecutorService executorService = Executors.newScheduledThreadPool(4);
/**
* Create a new property change support instance.
* @param changeSupport the support type, not null.
* @param propertySource the property source to pass to listeners, not null.
*/
public PropertySourceChangeSupport(ChangeSupport changeSupport,
PropertySource propertySource){
this.changeSupport = Objects.requireNonNull(changeSupport);
this.propertySource = Objects.requireNonNull(propertySource);
}
public ChangeSupport getChangeSupport() {
return changeSupport;
}
public String getVersion() {
return version.get() + ": timestamp="+timestamp;
}
public void addChangeListener(BiConsumer<Set<String>, PropertySource> l){
switch(changeSupport){
case SUPPORTED:
if(!listeners.contains(l)){
listeners.add(l);
}
break;
case UNSUPPORTED:
case IMMUTABLE:
default:
break;
}
}
public void removeChangeListener(BiConsumer<Set<String>, PropertySource> l){
if(changeSupport==ChangeSupport.SUPPORTED) {
listeners.remove(l);
}
}
public void removeAllChangeListeners(){
if(changeSupport==ChangeSupport.SUPPORTED) {
listeners.clear();
}
}
public long load(Map<String, PropertyValue> properties){
Objects.requireNonNull(properties);
if(changeSupport==ChangeSupport.SUPPORTED) {
Set<String> changedKeys = calculateChangedKeys(this.valueMap, properties);
if(!changedKeys.isEmpty()) {
this.valueMap = properties;
version.incrementAndGet();
fireListeners(changedKeys);
}
} else {
if(!properties.equals(this.valueMap)){
this.valueMap = properties;
version.incrementAndGet();
}
}
return version.get();
}
private Set<String> calculateChangedKeys(Map<String, PropertyValue> valueMap, Map<String, PropertyValue> newValues) {
Set<String> result = new HashSet<>();
if(this.valueMap!=null) {
for (Map.Entry<String, PropertyValue> en : valueMap.entrySet()) {
if (!newValues.containsKey(en.getKey())) {
result.add(en.getKey()); // removed
}
}
}
for(Map.Entry<String, PropertyValue> en:newValues.entrySet()){
if(valueMap != null){
if(!valueMap.containsKey(en.getKey())) {
result.add(en.getKey()); // added
}
if(!Objects.equals(valueMap.get(en.getKey()), en.getValue())) {
result.add(en.getKey()); // changed
}
}else{
result.add(en.getKey()); // added
}
}
return result;
}
private void fireListeners(Set<String> changedKeys) {
for(BiConsumer<Set<String>, PropertySource> l:this.listeners){
try{
l.accept(changedKeys, propertySource);
}catch(Exception e){
LOG.log(Level.WARNING, "Failed to load listener on property source change: " + l, e);
}
}
}
public PropertyValue getValue(String key){
if(valueMap==null){
return null;
}
return valueMap.get(key);
}
public Map<String, PropertyValue> getProperties(){
if(valueMap==null){
return Collections.emptyMap();
}
return Collections.unmodifiableMap(valueMap);
}
public void scheduleChangeMonitor(Supplier<Map<String, PropertyValue>> propertySupplier, long duration, TimeUnit timeUnit){
if(changeSupport==ChangeSupport.SUPPORTED) {
Objects.requireNonNull(propertySupplier);
scheduleTask = executorService.schedule(() -> {
load(propertySupplier.get());
}, duration, timeUnit);
}
}
public void cancelSchedule(){
if(changeSupport==ChangeSupport.SUPPORTED && scheduleTask!=null){
scheduleTask.cancel(false);
}
}
private int hashCode(Map<String, PropertyValue> valueMap) {
int result = 0;
for(Map.Entry<String,PropertyValue> en:valueMap.entrySet()) {
result = 31 * result + en.getKey().hashCode();
String value = en.getValue().getValue();
result = 31 * result + (value!=null?value.hashCode():0);
}
return result;
}
}