| /** |
| * Licensed 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.aries.cdi.container.internal.container; |
| |
| import java.util.Arrays; |
| import java.util.Dictionary; |
| import java.util.Hashtable; |
| import java.util.Objects; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| |
| import org.apache.aries.cdi.container.internal.container.Op.Mode; |
| import org.apache.aries.cdi.container.internal.container.Op.Type; |
| import org.apache.aries.cdi.container.internal.model.Component; |
| import org.apache.aries.cdi.container.internal.model.ExtendedComponentInstanceDTO; |
| import org.apache.aries.cdi.container.internal.model.ExtendedConfigurationDTO; |
| import org.apache.aries.cdi.container.internal.util.Maps; |
| import org.apache.aries.cdi.container.internal.util.Predicates; |
| import org.apache.aries.cdi.container.internal.util.Syncro; |
| import org.apache.aries.cdi.container.internal.util.Throw; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.ServiceRegistration; |
| import org.osgi.service.cdi.ConfigurationPolicy; |
| import org.osgi.service.cdi.MaximumCardinality; |
| import org.osgi.service.cdi.runtime.dto.template.ConfigurationTemplateDTO; |
| import org.osgi.service.cm.ConfigurationEvent; |
| import org.osgi.service.log.Logger; |
| import org.osgi.util.promise.Promise; |
| |
| public class ConfigurationListener extends Phase implements org.osgi.service.cm.ConfigurationListener { |
| |
| public static class Builder { |
| |
| public Builder(ContainerState containerState) { |
| _containerState = containerState; |
| } |
| |
| public Builder component(Component component) { |
| _component = component; |
| return this; |
| } |
| |
| public ConfigurationListener build() { |
| Objects.requireNonNull(_component); |
| return new ConfigurationListener(_containerState, _component); |
| } |
| |
| private Component _component; |
| private final ContainerState _containerState; |
| |
| } |
| |
| protected ConfigurationListener( |
| ContainerState containerState, |
| Component component) { |
| |
| super(containerState, component); |
| _component = component; |
| _log = containerState.containerLogs().getLogger(getClass()); |
| } |
| |
| @Override |
| public boolean close() { |
| try (Syncro open = syncro.open()) { |
| if (_listenerService == null) { |
| return true; |
| } |
| else { |
| _listenerService.unregister(); |
| _listenerService = null; |
| } |
| |
| return next.map( |
| next -> { |
| submit(next.closeOp(), next::close).onFailure( |
| f -> { |
| _log.error(l -> l.error("CCR Failure in configuration listener close on {}", next, f)); |
| |
| error(f); |
| } |
| ); |
| |
| return true; |
| } |
| ).orElse(true); |
| } |
| } |
| |
| @Override |
| public Op closeOp() { |
| return Op.of(Mode.CLOSE, Type.CONFIGURATION_LISTENER, _component.template().name); |
| } |
| |
| @Override |
| public void configurationEvent(ConfigurationEvent event) { |
| next.map(next -> (Component)next).ifPresent( |
| next -> next.configurationTemplates().stream().filter( |
| t -> Predicates.isMatchingConfiguration(event).test(t) |
| ).findFirst().ifPresent( |
| t -> { |
| String eventString = Arrays.asList(event.getPid(), event.getFactoryPid(), type(event)).toString(); |
| |
| Promise<Boolean> result = containerState.submit( |
| Op.of(Mode.OPEN, Type.CONFIGURATION_EVENT, eventString), |
| () -> { |
| _log.debug(l -> l.debug("CCR Event {} matched {} because of {}", eventString, _component.template().name, _component.template().configurations)); |
| processEvent(next, t, event); |
| return true; |
| } |
| ); |
| |
| try { |
| result.getValue(); |
| } |
| catch (Exception e) { |
| Throw.exception(e); |
| } |
| } |
| ) |
| ); |
| } |
| |
| @Override |
| public boolean open() { |
| try (Syncro open = syncro.open()) { |
| if (_listenerService != null) { |
| return true; |
| } |
| |
| if (containerState.bundleContext() == null) { |
| // this bundle was already removed |
| return false; |
| } |
| |
| Dictionary<String, Object> properties = new Hashtable<>(); |
| properties.put("name", toString()); |
| properties.put(Constants.SERVICE_DESCRIPTION, "Aries CDI - Configuration Listener for " + containerState.bundle()); |
| properties.put(Constants.SERVICE_VENDOR, "Apache Software Foundation"); |
| |
| _listenerService = containerState.bundleContext().registerService( |
| org.osgi.service.cm.ConfigurationListener.class, this, properties); |
| |
| return next.map(next -> (Component)next).map( |
| component -> { |
| submit(component.openOp(), component::open).then( |
| s -> { |
| component.configurationTemplates().stream().filter( |
| ct -> Objects.nonNull(ct.pid) |
| ).forEach( |
| template -> { |
| if (template.maximumCardinality == MaximumCardinality.ONE) { |
| containerState.findConfig(template.pid).ifPresent( |
| c -> processEvent( |
| component, |
| template, |
| new ConfigurationEvent( |
| containerState.caTracker().getServiceReference(), |
| ConfigurationEvent.CM_UPDATED, |
| null, |
| c.getPid())) |
| ); |
| } |
| else { |
| containerState.findConfigs(template.pid, true).ifPresent( |
| arr -> Arrays.stream(arr).forEach( |
| c -> processEvent( |
| component, |
| template, |
| new ConfigurationEvent( |
| containerState.caTracker().getServiceReference(), |
| ConfigurationEvent.CM_UPDATED, |
| c.getFactoryPid(), |
| c.getPid())) |
| ) |
| ); |
| } |
| } |
| ); |
| |
| return s; |
| }, |
| f -> { |
| _log.error(l -> l.error("CCR Failure during configuration start on {}", next, f.getFailure())); |
| |
| error(f.getFailure()); |
| } |
| ); |
| |
| return true; |
| } |
| ).orElse(true); |
| } |
| } |
| |
| @Override |
| public Op openOp() { |
| return Op.of(Mode.OPEN, Type.CONFIGURATION_LISTENER, _component.template().name); |
| } |
| |
| @Override |
| public String toString() { |
| return Arrays.asList(getClass().getSimpleName(), _component).toString(); |
| } |
| |
| private void processEvent(Component component, ConfigurationTemplateDTO t, ConfigurationEvent event) { |
| boolean required = t.policy == ConfigurationPolicy.REQUIRED; |
| boolean single = t.maximumCardinality == MaximumCardinality.ONE; |
| |
| switch (event.getType()) { |
| case ConfigurationEvent.CM_DELETED: |
| component.instances().stream().map( |
| ExtendedComponentInstanceDTO.class::cast |
| ).filter( |
| instance -> (!single && event.getPid().equals(instance.pid)) || single |
| ).forEach( |
| instance -> { |
| submit(instance.closeOp(), instance::close).then( |
| s -> { |
| if (!required) { |
| instance.configurations.removeIf( |
| c -> c.template == t |
| ); |
| |
| submit(instance.openOp(), instance::open); |
| } |
| else { |
| component.instances().remove(instance); |
| } |
| |
| return s; |
| } |
| ); |
| } |
| ); |
| return; |
| case ConfigurationEvent.CM_LOCATION_CHANGED: |
| break; |
| case ConfigurationEvent.CM_UPDATED: |
| if (!single && |
| !component.instances().stream().map( |
| ExtendedComponentInstanceDTO.class::cast |
| ).filter( |
| instance -> event.getPid().equals(instance.pid) |
| ).findFirst().isPresent()) { |
| |
| ExtendedComponentInstanceDTO instance = new ExtendedComponentInstanceDTO( |
| containerState, _component.activatorBuilder()); |
| instance.activations = new CopyOnWriteArrayList<>(); |
| instance.configurations = new CopyOnWriteArrayList<>(); |
| instance.pid = event.getPid(); |
| instance.references = new CopyOnWriteArrayList<>(); |
| instance.template = component.template(); |
| |
| component.instances().add(instance); |
| } |
| |
| containerState.findConfig(event.getPid()).ifPresent( |
| configuration -> { |
| ExtendedConfigurationDTO configurationDTO = new ExtendedConfigurationDTO(); |
| |
| configurationDTO.configuration = configuration; |
| configurationDTO.pid = configuration.getPid(); |
| configurationDTO.properties = Maps.of(configuration.getProcessedProperties(event.getReference())); |
| configurationDTO.template = t; |
| |
| component.instances().stream().map( |
| ExtendedComponentInstanceDTO.class::cast |
| ).filter( |
| instance -> (!single && event.getPid().equals(instance.pid)) || single |
| ).forEach( |
| instance -> { |
| submit(instance.closeOp(), instance::close).then( |
| s -> { |
| instance.configurations.removeIf(c -> c.template == t); |
| instance.configurations.add(configurationDTO); |
| |
| submit(instance.openOp(), instance::open); |
| |
| return s; |
| } |
| ); |
| } |
| ); |
| } |
| ); |
| break; |
| } |
| } |
| |
| private String type(ConfigurationEvent event) { |
| if (event.getType() == ConfigurationEvent.CM_DELETED) |
| return "DELETED"; |
| if (event.getType() == ConfigurationEvent.CM_LOCATION_CHANGED) |
| return "LOCATION_CHANGED"; |
| if (event.getType() == ConfigurationEvent.CM_UPDATED) |
| return "UPDATED"; |
| throw new IllegalArgumentException("CM Event type " + event.getType()); |
| } |
| |
| private volatile ServiceRegistration<org.osgi.service.cm.ConfigurationListener> _listenerService; |
| |
| private final Component _component; |
| private final Logger _log; |
| |
| } |