/* * 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 static org.apache.felix.dm.lambda.impl.ComponentBuilderImpl.ComponentCallback.DESTROY; import static org.apache.felix.dm.lambda.impl.ComponentBuilderImpl.ComponentCallback.INIT; import static org.apache.felix.dm.lambda.impl.ComponentBuilderImpl.ComponentCallback.START; import static org.apache.felix.dm.lambda.impl.ComponentBuilderImpl.ComponentCallback.STOP; import java.util.ArrayList; import java.util.Dictionary; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Properties; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; import org.apache.felix.dm.Component; import org.apache.felix.dm.ComponentStateListener; import org.apache.felix.dm.Dependency; import org.apache.felix.dm.DependencyManager; import org.apache.felix.dm.context.ComponentContext; import org.apache.felix.dm.lambda.BundleDependencyBuilder; import org.apache.felix.dm.lambda.ComponentBuilder; import org.apache.felix.dm.lambda.ConfigurationDependencyBuilder; import org.apache.felix.dm.lambda.DependencyBuilder; import org.apache.felix.dm.lambda.FluentProperty; import org.apache.felix.dm.lambda.FutureDependencyBuilder; import org.apache.felix.dm.lambda.ServiceDependencyBuilder; import org.apache.felix.dm.lambda.callbacks.InstanceCb; import org.apache.felix.dm.lambda.callbacks.InstanceCbComponent; public class ComponentBuilderImpl implements ComponentBuilder<ComponentBuilderImpl> { private final List<DependencyBuilder<?>> m_dependencyBuilders = new ArrayList<>(); private final List<Dependency> m_dependencies = new ArrayList<>(); private final Component m_component; private final boolean m_componentUpdated; private String[] m_serviceNames; private Dictionary<Object, Object> m_properties; private Object m_impl; private Object m_factory; private boolean m_factoryHasComposite; private boolean m_autoAdd = true; protected final Map<ComponentCallback, MethodRef> m_refs = new HashMap<>(); private Object m_compositionInstance; private String m_compositionMethod; private String m_init; private String m_start; private String m_stop; private String m_destroy; private String m_factoryCreateMethod; private boolean m_hasFactoryRef; private boolean m_hasFactory; private Object m_initCallbackInstance; private Object m_startCallbackInstance; private Object m_stopCallbackInstance; private Object m_destroyCallbackInstance; private final List<ComponentStateListener> m_listeners = new ArrayList<>(); enum ComponentCallback { INIT, START, STOP, DESTROY }; @FunctionalInterface interface MethodRef { public void accept(Component c); } public ComponentBuilderImpl(DependencyManager dm) { m_component = dm.createComponent(); m_componentUpdated = false; } public ComponentBuilderImpl(Component component, boolean update) { m_component = component; m_componentUpdated = update; } @Override public ComponentBuilderImpl autoConfig(Class<?> clazz, boolean autoConfig) { m_component.setAutoConfig(clazz, autoConfig); return this; } @Override public ComponentBuilderImpl autoConfig(Class<?> clazz, String instanceName) { m_component.setAutoConfig(clazz, instanceName); return this; } @Override public ComponentBuilderImpl provides(Class<?> iface) { m_serviceNames = new String[] {iface.getName()}; return this; } @Override public ComponentBuilderImpl provides(Class<?> iface, String name, Object value, Object ... rest) { provides(iface); properties(name, value, rest); return this; } @Override public ComponentBuilderImpl provides(Class<?> iface, FluentProperty ... properties) { provides(iface); properties(properties); return this; } @Override public ComponentBuilderImpl provides(Class<?> iface, Dictionary<?,?> properties) { provides(iface); properties(properties); return this; } @Override public ComponentBuilderImpl provides(Class<?>[] ifaces) { m_serviceNames = Stream.of(ifaces).map(c -> c.getName()).toArray(String[]::new); return this; } @Override public ComponentBuilderImpl provides(Class<?>[] ifaces, String name, Object value, Object ... rest) { provides(ifaces); properties(name, value, rest); return this; } @Override public ComponentBuilderImpl provides(Class<?>[] ifaces, FluentProperty ... properties) { provides(ifaces); properties(properties); return this; } @Override public ComponentBuilderImpl provides(Class<?>[] ifaces, Dictionary<?,?> properties) { provides(ifaces); properties(properties); return this; } @Override public ComponentBuilderImpl provides(String iface) { m_serviceNames = new String[] {iface}; return this; } @Override public ComponentBuilderImpl provides(String iface, String name, Object value, Object ... rest) { provides(iface); properties(name, value, rest); return this; } @Override public ComponentBuilderImpl provides(String iface, FluentProperty ... properties) { provides(iface); properties(properties); return this; } @Override public ComponentBuilderImpl provides(String iface, Dictionary<?,?> properties) { provides(iface); properties(properties); return this; } @Override public ComponentBuilderImpl provides(String[] ifaces) { m_serviceNames = ifaces; return this; } @Override public ComponentBuilderImpl provides(String[] ifaces, String name, Object value, Object ... rest) { provides(ifaces); properties(name, value, rest); return this; } @Override public ComponentBuilderImpl provides(String[] ifaces, FluentProperty ... properties) { provides(ifaces); properties(properties); return this; } @Override public ComponentBuilderImpl provides(String[] ifaces, Dictionary<?,?> properties) { provides(ifaces); properties(properties); return this; } @SuppressWarnings("unchecked") @Override public ComponentBuilderImpl properties(Dictionary<?, ?> properties) { m_properties = (Dictionary<Object, Object>) properties; return this; } @Override public ComponentBuilderImpl properties(String name, Object value, Object ... rest) { Objects.nonNull(name); Objects.nonNull(value); Properties props = new Properties(); props.put(name, value); if ((rest.length & 1) != 0) { throw new IllegalArgumentException("Invalid number of specified properties (number of arguments must be even)."); } for (int i = 0; i < rest.length - 1; i += 2) { String k = rest[i].toString().trim(); Object v = rest[i+1]; props.put(k, v); } m_properties = props; return this; } @Override public ComponentBuilderImpl properties(FluentProperty ... properties) { Dictionary<Object, Object> props = new Hashtable<>(); Stream.of(properties).forEach(property -> { String name = Helpers.getLambdaParameterName(property, 0); if (name.equals("arg0")) { throw new IllegalArgumentException("arg0 property name not supported"); } Object value = property.apply(name); props.put(convertDots(name), value); }); m_properties = props; return this; } @Override public ComponentBuilderImpl debug(String label) { m_component.setDebug(label); return this; } @Override public ComponentBuilderImpl autoAdd(boolean autoAdd) { m_autoAdd = autoAdd; return this; } public ComponentBuilderImpl autoAdd() { m_autoAdd = true; return this; } public boolean isAutoAdd() { return m_autoAdd; } @Override public ComponentBuilderImpl impl(Object instance) { m_impl = instance; return this; } @Override public ComponentBuilderImpl factory(Object factory, String createMethod) { m_factory = factory; m_factoryCreateMethod = createMethod; ensureHasNoFactoryRef(); m_hasFactory = true; return this; } @Override public ComponentBuilderImpl factory(Supplier<?> create) { Objects.nonNull(create); ensureHasNoFactory(); m_hasFactoryRef = true; m_factory = new Object() { @SuppressWarnings("unused") public Object create() { return create.get(); } @Override public String toString() { return create.getClass().getName() + " (Factory)"; } }; return this; } @Override public <U, V> ComponentBuilderImpl factory(Supplier<U> supplier, Function<U, V> create) { Objects.nonNull(supplier); Objects.nonNull(create); ensureHasNoFactory(); m_hasFactoryRef = true; m_factory = new Object() { @SuppressWarnings("unused") public Object create() { U factoryImpl = supplier.get(); return create.apply(factoryImpl); } @Override public String toString() { return supplier.getClass().getName() + " (Factory)"; } }; return this; } @Override public ComponentBuilderImpl factory(Supplier<?> create, Supplier<Object[]> getComposite) { Objects.nonNull(create); Objects.nonNull(getComposite); ensureHasNoFactory(); m_hasFactoryRef = true; m_factory = new Object() { @SuppressWarnings("unused") public Object create() { // Create Factory instance return create.get(); } @SuppressWarnings("unused") public Object[] getComposite() { // Create Factory instance return getComposite.get(); } @Override public String toString() { return create.getClass().getName() + " (Factory)"; } }; m_factoryHasComposite = true; return this; } @Override public <U> ComponentBuilderImpl factory(Supplier<U> factorySupplier, Function<U, ?> factoryCreate, Function<U, Object[]> factoryGetComposite) { Objects.nonNull(factorySupplier); Objects.nonNull(factoryCreate); Objects.nonNull(factoryGetComposite); ensureHasNoFactory(); m_hasFactoryRef = true; m_factory = new Object() { U m_factoryInstance; @SuppressWarnings("unused") public Object create() { m_factoryInstance = factorySupplier.get(); return factoryCreate.apply(m_factoryInstance); } @SuppressWarnings("unused") public Object[] getComposite() { return factoryGetComposite.apply(m_factoryInstance); } @Override public String toString() { return factorySupplier.getClass().getName() + " (Factory)"; } }; m_factoryHasComposite = true; return this; } public ComponentBuilderImpl composition(String getCompositionMethod) { return composition(null, getCompositionMethod); } public ComponentBuilderImpl composition(Object instance, String getCompositionMethod) { m_compositionInstance = instance; m_compositionMethod = getCompositionMethod; return this; } public ComponentBuilderImpl composition(Supplier<Object[]> getCompositionMethod) { m_compositionInstance = new Object() { @SuppressWarnings("unused") public Object[] getComposition() { return getCompositionMethod.get(); } }; m_compositionMethod = "getComposition"; return this; } @Override public ComponentBuilderImpl withDep(Dependency dep) { m_dependencies.add(dep); return this; } @Override public <U> ComponentBuilderImpl withSvc(Class<U> service, Consumer<ServiceDependencyBuilder<U>> consumer) { ServiceDependencyBuilder<U> dep = new ServiceDependencyBuilderImpl<>(m_component, service); consumer.accept(dep); m_dependencyBuilders.add(dep); return this; } @Override public ComponentBuilderImpl withCnf(Consumer<ConfigurationDependencyBuilder> consumer) { ConfigurationDependencyBuilder dep = new ConfigurationDependencyBuilderImpl(m_component); consumer.accept(dep); m_dependencyBuilders.add(dep); return this; } @Override public ComponentBuilderImpl withBundle(Consumer<BundleDependencyBuilder> consumer) { BundleDependencyBuilder dep = new BundleDependencyBuilderImpl(m_component); consumer.accept(dep); m_dependencyBuilders.add(dep); return this; } @Override public <V> ComponentBuilderImpl withFuture(CompletableFuture<V> future, Consumer<FutureDependencyBuilder<V>> consumer) { FutureDependencyBuilder<V> dep = new CompletableFutureDependencyImpl<>(m_component, future); consumer.accept(dep); m_dependencyBuilders.add(dep); return this; } public ComponentBuilderImpl init(String callback) { m_init = callback; return this; } public ComponentBuilderImpl init(Object callbackInstance, String callback) { init(callback); m_initCallbackInstance = callbackInstance; return this; } @Override public ComponentBuilderImpl init(InstanceCb callback) { setCallbackMethodRef(INIT, component -> callback.callback()); m_init = null; m_initCallbackInstance = null; return this; } @Override public ComponentBuilderImpl init(InstanceCbComponent callback) { setCallbackMethodRef(INIT, component -> callback.accept(component)); m_init = null; m_initCallbackInstance = null; return this; } public ComponentBuilderImpl start(String callback) { m_start = callback; return this; } public ComponentBuilderImpl start(Object callbackInstance, String callback) { start(callback); m_startCallbackInstance = callbackInstance; return this; } @Override public ComponentBuilderImpl start(InstanceCb callback) { setCallbackMethodRef(START, component -> callback.callback()); m_start = null; m_startCallbackInstance = null; return this; } @Override public ComponentBuilderImpl start(InstanceCbComponent callback) { setCallbackMethodRef(START, component -> callback.accept(component)); m_start = null; m_startCallbackInstance = null; return this; } public ComponentBuilderImpl stop(String callback) { m_stop = callback; return this; } public ComponentBuilderImpl stop(Object callbackInstance, String callback) { stop(callback); m_stopCallbackInstance = callbackInstance; return this; } @Override public ComponentBuilderImpl stop(InstanceCb callback) { setCallbackMethodRef(STOP, component -> callback.callback()); m_stop = null; m_stopCallbackInstance = null; return this; } @Override public ComponentBuilderImpl stop(InstanceCbComponent callback) { setCallbackMethodRef(STOP, component -> callback.accept(component)); m_stop = null; m_stopCallbackInstance = null; return this; } public ComponentBuilderImpl destroy(String callback) { m_destroy = callback; return this; } public ComponentBuilderImpl destroy(Object callbackInstance, String callback) { destroy(callback); m_destroyCallbackInstance = callbackInstance; return this; } @Override public ComponentBuilderImpl destroy(InstanceCb callback) { setCallbackMethodRef(DESTROY, component -> callback.callback()); m_destroy = null; m_destroyCallbackInstance = null; return this; } @Override public ComponentBuilderImpl destroy(InstanceCbComponent callback) { setCallbackMethodRef(DESTROY, component -> callback.accept(component)); m_destroy = null; m_destroyCallbackInstance = null; return this; } public ComponentBuilderImpl listener(ComponentStateListener listener) { m_listeners.add(listener); return this; } public Component build() { if (m_serviceNames != null) { m_component.setInterface(m_serviceNames, m_properties); } if (m_properties != null) { m_component.setServiceProperties(m_properties); } m_listeners.stream().forEach(m_component::add); if (! m_componentUpdated) { // Don't override impl or set callbacks if component is being updated if (m_impl != null) { m_component.setImplementation(m_impl); m_component.setComposition(m_compositionInstance, m_compositionMethod); } else { Objects.nonNull(m_factory); if (m_hasFactoryRef) { m_component.setFactory(m_factory, "create"); if (m_factoryHasComposite) { m_component.setComposition(m_factory, "getComposite"); } } else { m_component.setFactory(m_factory, m_factoryCreateMethod); } } if (hasCallbacks()) { // either method refs on some object instances, or a callback (reflection) on some object instances. if (m_refs.get(INIT) == null) { setCallbackMethodRef(INIT, component -> invokeCallbacks(component, m_initCallbackInstance, m_init, "init")); } if (m_refs.get(START) == null) { setCallbackMethodRef(START, component -> invokeCallbacks(component, m_startCallbackInstance, m_start, "start")); } if (m_refs.get(STOP) == null) { setCallbackMethodRef(STOP, component -> invokeCallbacks(component, m_stopCallbackInstance, m_stop, "stop")); } if (m_refs.get(DESTROY) == null) { setCallbackMethodRef(DESTROY, component -> invokeCallbacks(component, m_destroyCallbackInstance, m_destroy, "destroy")); } setInternalCallbacks(); } } if (m_dependencyBuilders.size() > 0) { // add atomically in case we are building some component dependencies from a component init method. List<Dependency> depList = new ArrayList<>(); m_dependencyBuilders.stream().map(builder -> builder.build()).forEach(depList::add); depList.addAll(m_dependencies); m_component.add(depList.stream().toArray(Dependency[]::new)); } return m_component; } private boolean hasCallbacks() { return m_refs.size() > 0 || m_init != null || m_start != null || m_stop != null || m_destroy != null; } private void invokeCallbacks(Component component, Object callbackInstance, String callback, String defaultCallback) { boolean logIfNotFound = (callback != null); callback = callback != null ? callback : defaultCallback; ComponentContext ctx = (ComponentContext) component; Object[] instances = callbackInstance != null ? new Object[] { callbackInstance } : ctx.getInstances(); ctx.invokeCallbackMethod(instances, callback, new Class[][] {{ Component.class }, {}}, new Object[][] {{ component }, {}}, logIfNotFound); } private ComponentBuilderImpl setCallbackMethodRef(ComponentCallback cbType, MethodRef ref) { m_refs.put(cbType, ref); return this; } @SuppressWarnings("unused") private void setInternalCallbacks() { Object cb = new Object() { void init(Component comp) { invokeLifecycleCallback(ComponentCallback.INIT, comp); } void start(Component comp) { invokeLifecycleCallback(ComponentCallback.START, comp); } void stop(Component comp) { invokeLifecycleCallback(ComponentCallback.STOP, comp); } void destroy(Component comp) { invokeLifecycleCallback(ComponentCallback.DESTROY, comp); } }; m_component.setCallbacks(cb, "init", "start", "stop", "destroy"); } private void invokeLifecycleCallback(ComponentCallback cbType, Component component) { m_refs.computeIfPresent(cbType, (k, mref) -> { mref.accept(component); return mref; }); } private void ensureHasNoFactoryRef() { if (m_hasFactoryRef) { throw new IllegalStateException("Can't mix factory method name and factory method reference"); } } private void ensureHasNoFactory() { if (m_hasFactory) { throw new IllegalStateException("Can't mix factory method name and factory method reference"); } } private String convertDots(String propertyName) { StringBuilder sb = new StringBuilder(propertyName); // replace "__" by "_" or "_" by ".": foo_bar -> foo.bar; foo__BAR_zoo -> foo_BAR.zoo for (int i = 0; i < sb.length(); i ++) { if (sb.charAt(i) == '_') { if (i < (sb.length() - 1) && sb.charAt(i+1) == '_') { // replace foo__bar -> foo_bar sb.replace(i, i+2, "_"); } else { // replace foo_bar -> foo.bar sb.replace(i, i+1, "."); } } } return sb.toString(); } }