/* * 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.felix.dm.lambda.impl; import java.util.ArrayList; import java.util.Dictionary; import java.util.List; import java.util.Objects; import java.util.stream.Stream; import org.apache.felix.dm.Component; import org.apache.felix.dm.ConfigurationDependency; import org.apache.felix.dm.context.ComponentContext; import org.apache.felix.dm.lambda.ConfigurationDependencyBuilder; import org.apache.felix.dm.lambda.callbacks.CbConfiguration; import org.apache.felix.dm.lambda.callbacks.CbConfigurationComponent; import org.apache.felix.dm.lambda.callbacks.CbDictionary; import org.apache.felix.dm.lambda.callbacks.CbDictionaryComponent; import org.apache.felix.dm.lambda.callbacks.InstanceCbConfiguration; import org.apache.felix.dm.lambda.callbacks.InstanceCbConfigurationComponent; import org.apache.felix.dm.lambda.callbacks.InstanceCbDictionary; import org.apache.felix.dm.lambda.callbacks.InstanceCbDictionaryComponent; public class ConfigurationDependencyBuilderImpl implements ConfigurationDependencyBuilder { private String m_pid; private boolean m_propagate; private boolean m_required = true; private final Component m_component; private String m_updateMethodName = "updated"; private Object m_updateCallbackInstance; private boolean m_hasMethodRefs; private boolean m_hasReflectionCallback; private final List<MethodRef<Object>> m_refs = new ArrayList<>(); private boolean m_hasComponentCallbackRefs; private Class<?> m_configType; @FunctionalInterface interface MethodRef<I> { public void accept(I instance, Component c, Dictionary<String, Object> props); } public ConfigurationDependencyBuilderImpl(Component component) { m_component = component; } @Override public ConfigurationDependencyBuilder pid(String pid) { m_pid = pid; return this; } @Override public ConfigurationDependencyBuilder propagate() { m_propagate = true; return this; } @Override public ConfigurationDependencyBuilder propagate(boolean propagate) { m_propagate = propagate; return this; } @Override public ConfigurationDependencyBuilder required(boolean required) { m_required = required; return this; } @Override public ConfigurationDependencyBuilder required() { m_required = true; return this; } @Override public ConfigurationDependencyBuilder optional() { m_required = false; return this; } public ConfigurationDependencyBuilder update(String update) { checkHasNoMethodRefs(); m_hasReflectionCallback = true; m_updateMethodName = update; return this; } public ConfigurationDependencyBuilder update(Class<?> configType, String updateMethod) { m_configType = configType; return update(updateMethod); } public ConfigurationDependencyBuilder update(Object callbackInstance, String update) { m_updateCallbackInstance = callbackInstance; update(update); return this; } public ConfigurationDependencyBuilder update(Class<?> configType, Object callbackInstance, String update) { m_updateCallbackInstance = callbackInstance; return update(callbackInstance, update); } @Override public <T> ConfigurationDependencyBuilder update(CbDictionary<T> callback) { Class<T> componentType = Helpers.getLambdaArgType(callback, 0); return setComponentCallbackRef(componentType, (instance, component, props) -> { callback.accept((T) instance, props); }); } @Override public <T> ConfigurationDependencyBuilder update(CbDictionaryComponent<T> callback) { Class<T> componentType = Helpers.getLambdaArgType(callback, 0); return setComponentCallbackRef(componentType, (instance, component, props) -> { callback.accept((T) instance, props, component); }); } @Override public <T, U> ConfigurationDependencyBuilder update(Class<U> configClass, CbConfiguration<T, U> callback) { Class<T> componentType = Helpers.getLambdaArgType(callback, 0); m_pid = m_pid == null ? configClass.getName() : m_pid; return setComponentCallbackRef(componentType, (instance, component, props) -> { U configProxy = ((ComponentContext) m_component).createConfigurationType(configClass, props); callback.accept((T) instance, configProxy); }); } @Override public <T, U> ConfigurationDependencyBuilder update(Class<U> configClass, CbConfigurationComponent<T, U> callback) { Class<T> componentType = Helpers.getLambdaArgType(callback, 0); m_pid = m_pid == null ? configClass.getName() : m_pid; return setComponentCallbackRef(componentType, (instance, component, props) -> { U configProxy = ((ComponentContext) m_component).createConfigurationType(configClass, props); callback.accept((T) instance, configProxy, component); }); } @Override public ConfigurationDependencyBuilder update(InstanceCbDictionary callback) { return setInstanceCallbackRef((instance, component, props) -> { callback.accept(props); }); } @Override public ConfigurationDependencyBuilder update(InstanceCbDictionaryComponent callback) { return setInstanceCallbackRef((instance, component, props) -> { callback.accept(props, component); }); } public <T> ConfigurationDependencyBuilder update(Class<T> configClass, InstanceCbConfiguration<T> updated) { m_pid = m_pid == null ? configClass.getName() : m_pid; return setInstanceCallbackRef((instance, component, props) -> { T configProxy = ((ComponentContext) m_component).createConfigurationType(configClass, props); updated.accept(configProxy); }); } public <T> ConfigurationDependencyBuilder update(Class<T> configClass, InstanceCbConfigurationComponent<T> updated) { m_pid = m_pid == null ? configClass.getName() : m_pid; return setInstanceCallbackRef((instance, component, props) -> { T configProxy = ((ComponentContext) m_component).createConfigurationType(configClass, props); updated.accept(configProxy, component); }); } @Override public ConfigurationDependency build() { String pid = m_pid == null ? (m_configType != null ? m_configType.getName() : null) : m_pid; if (pid == null) { throw new IllegalStateException("Pid not specified"); } ConfigurationDependency dep = m_component.getDependencyManager().createConfigurationDependency(); Objects.nonNull(m_pid); dep.setPid(pid); dep.setPropagate(m_propagate); dep.setRequired(m_required); if (m_updateMethodName != null) { dep.setCallback(m_updateCallbackInstance, m_updateMethodName, m_configType); } else if (m_refs.size() > 0) { // setup an internal callback object. When config is updated, we have to call each registered // method references. // Notice that we need the component to be instantiated in case there is a mref on one of the component instances (unbound method ref), or is used // called "needsInstance(true)". dep.setCallback(new Object() { @SuppressWarnings("unused") void updated(Component comp, Dictionary<String, Object> props) { m_refs.forEach(mref -> mref.accept(null, comp, props)); } }, "updated", m_hasComponentCallbackRefs); } return dep; } private <T> ConfigurationDependencyBuilder setInstanceCallbackRef(MethodRef<T> ref) { checkHasNoReflectionCallbacks(); m_hasMethodRefs = true; m_updateMethodName = null; m_refs.add((instance, component, props) -> ref.accept(null, component, props)); return this; } @SuppressWarnings("unchecked") private <T> ConfigurationDependencyBuilder setComponentCallbackRef(Class<T> type, MethodRef<T> ref) { checkHasNoReflectionCallbacks(); m_updateMethodName = null; m_hasMethodRefs = true; m_hasComponentCallbackRefs = true; m_refs.add((instance, component, props) -> { Object componentImpl = Stream.of(component.getInstances()) .filter(impl -> type.isAssignableFrom(Helpers.getClass(impl))) .findFirst() .orElseThrow(() -> new IllegalStateException("The method reference " + ref + " does not match any available component impl classes.")); ref.accept((T) componentImpl, component, props); }); return this; } private void checkHasNoMethodRefs() { if (m_hasMethodRefs) { throw new IllegalStateException("Can't mix method references with reflection based callbacks"); } } private void checkHasNoReflectionCallbacks() { if (m_hasReflectionCallback) { throw new IllegalStateException("Can't mix method references with reflection based callbacks"); } } }