blob: 494de1cf29b5e3dcea42b1d8447b52969d99368a [file] [log] [blame]
/**
* 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) {
_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 (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;
}