blob: 68028ad73050b1c141f39873783c491830392cbc [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 static org.apache.aries.cdi.container.internal.util.Filters.asFilter;
import java.util.Iterator;
import java.util.List;
import java.util.SortedSet;
import java.util.concurrent.ConcurrentSkipListSet;
import javax.enterprise.inject.spi.Extension;
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.ExtendedExtensionDTO;
import org.apache.aries.cdi.container.internal.model.ExtendedExtensionTemplateDTO;
import org.apache.aries.cdi.container.internal.util.Conversions;
import org.apache.aries.cdi.container.internal.util.Perms;
import org.apache.aries.cdi.container.internal.util.SRs;
import org.apache.aries.cdi.container.internal.util.Syncro;
import org.apache.aries.cdi.container.internal.util.Throw;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cdi.runtime.dto.ExtensionDTO;
import org.osgi.service.cdi.runtime.dto.template.ExtensionTemplateDTO;
import org.osgi.service.log.Logger;
import org.osgi.util.promise.Promise;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
public class ExtensionPhase extends Phase {
public ExtensionPhase(ContainerState containerState, Phase next) {
super(containerState, next);
_log = containerState.containerLogs().getLogger(getClass());
}
@Override
public boolean close() {
try (Syncro open = syncro.open()) {
if (extensionTemplates().isEmpty()) {
next.map(
next -> {
submit(next.closeOp(), next::close).onFailure(
f -> {
_log.error(l -> l.error("CCR Error in extension CLOSE on {}", bundle(), f));
error(f);
}
);
return true;
}
).orElse(true);
}
if (_extensionTracker != null) {
_extensionTracker.close();
_extensionTracker = null;
}
return true;
}
}
@Override
public Op closeOp() {
return Op.of(Mode.CLOSE, Type.EXTENSION, containerState.id());
}
@Override
public boolean open() {
try (Syncro open = syncro.open()) {
if (containerState.bundleContext() == null) {
// this bundle was already removed
return false;
}
_extensionTracker = new ServiceTracker<>(
containerState.bundleContext(), createExtensionFilter(), new ExtensionPhaseCustomizer());
_extensionTracker.open();
if (extensionTemplates().isEmpty()) {
next.map(
next -> {
submit(next.openOp(), next::open).onFailure(
f -> {
_log.error(l -> l.error("CCR Error in extension OPEN on {}", bundle(), f));
error(f);
}
);
return true;
}
).orElse(true);
}
return true;
}
}
@Override
public Op openOp() {
return Op.of(Mode.OPEN, Type.EXTENSION, containerState.id());
}
Filter createExtensionFilter() {
final List<ExtensionTemplateDTO> templates = extensionTemplates();
StringBuilder sb = new StringBuilder("(&(objectClass=" + Extension.class.getName() + ")");
sb.append("(|");
for (ExtensionTemplateDTO tmpl : templates) {
sb.append(tmpl.serviceFilter);
}
sb.append("(aries.cdi.extension.mode=implicit))");
sb.append(")");
return asFilter(sb.toString());
}
List<ExtensionTemplateDTO> extensionTemplates() {
return containerState.containerDTO().template.extensions;
}
List<ExtensionDTO> snapshots() {
return containerState.containerDTO().extensions;
}
private ServiceTracker<Extension, ExtendedExtensionDTO> _extensionTracker;
private final Logger _log;
private final SortedSet<ExtendedExtensionDTO> _references = new ConcurrentSkipListSet<>(
(e1, e2) -> e1.serviceReference.compareTo(e2.serviceReference)
);
private final Filter implicitFilter = asFilter("(aries.cdi.extension.mode=implicit)");
private class ExtensionPhaseCustomizer implements ServiceTrackerCustomizer<Extension, ExtendedExtensionDTO> {
@Override
public ExtendedExtensionDTO addingService(ServiceReference<Extension> reference) {
if (!Perms.hasExtensionServicePermission(containerState.bundleContext())) {
return null;
}
ExtendedExtensionTemplateDTO template = extensionTemplates().stream().map(
t -> (ExtendedExtensionTemplateDTO)t
).filter(
t -> t.filter.match(reference)
).findFirst().orElseGet(() -> {
if (implicitFilter.match(reference)) {
ExtendedExtensionTemplateDTO implicitTemplate = new ExtendedExtensionTemplateDTO();
implicitTemplate.filter = asFilter("(&(aries.cdi.extension.mode=implicit)(service.id=%s))", reference.getProperty(Constants.SERVICE_ID));
implicitTemplate.serviceFilter = implicitTemplate.filter.toString();
return implicitTemplate;
}
return null;
});
ExtendedExtensionDTO snapshot = snapshots().stream().map(
s -> (ExtendedExtensionDTO)s
).filter(
s -> s.template == template
).findFirst().orElse(null);
if (snapshot != null) {
if (reference.compareTo(snapshot.serviceReference) <= 0) {
return null;
}
if (snapshots().remove(snapshot)) {
_references.add(snapshot);
snapshot.extension = null;
containerState.bundleContext().ungetService(snapshot.serviceReference);
}
}
ExtendedExtensionDTO extensionDTO = new ExtendedExtensionDTO();
BundleContext bc = containerState.bundleContext();
extensionDTO.extension = bc.getServiceObjects(reference);
extensionDTO.service = SRs.from(reference);
extensionDTO.serviceReference = reference;
extensionDTO.template = template;
snapshots().add(extensionDTO);
containerState.incrementChangeCount();
next.ifPresent(
next -> submit(next.closeOp(), next::close).then(
s -> {
if (extensionTemplates().stream().allMatch(tmpl -> snapshots().stream().anyMatch(ext -> ext.template == tmpl))) {
return submit(next.openOp(), next::open).onFailure(
f -> {
_log.error(l -> l.error("CCR Error in extension open TRACKING {} on {}", reference, bundle(), f));
error(f);
}
);
}
return s;
},
f -> {
_log.error(l -> l.error("CCR Error extension close TRACKING {} on {}", reference, bundle(), f.getFailure()));
error(f.getFailure());
}
)
);
return extensionDTO;
}
@Override
public void modifiedService(ServiceReference<Extension> reference, ExtendedExtensionDTO extentionDTO) {
removedService(reference, extentionDTO);
addingService(reference);
}
@Override
public void removedService(ServiceReference<Extension> reference, final ExtendedExtensionDTO extensionDTO) {
_log.debug(l -> l.debug("CCR Departing extension {} on {}", Conversions.convert(extensionDTO).to(ExtensionDTO.class), bundle()));
containerState.bundleContext().ungetService(reference);
if (!snapshots().removeIf(snap ->
((ExtendedExtensionDTO)snap).serviceReference.equals(reference))) {
return;
}
for (Iterator<ExtendedExtensionDTO> itr = _references.iterator();itr.hasNext();) {
ExtendedExtensionDTO entry = itr.next();
if (((ExtendedExtensionTemplateDTO)extensionDTO.template).filter.match(entry.serviceReference)) {
entry.extension = containerState.bundleContext().getServiceObjects(entry.serviceReference);
itr.remove();
snapshots().add(entry);
break;
}
}
containerState.incrementChangeCount();
next.ifPresent(
next -> {
Promise<Boolean> result = submit(next.closeOp(), next::close).then(
s -> {
if (extensionTemplates().stream().allMatch(tmpl -> snapshots().stream().anyMatch(ext -> ext.template == tmpl))) {
return submit(next.openOp(), next::open).onFailure(
f -> {
_log.error(l -> l.error("CCR Error in extension open {} on {}", reference, bundle(), f));
error(f);
}
);
}
return s;
},
f -> {
_log.error(l -> l.error("CCR Error in extension close {} on {}", reference, bundle(), f.getFailure()));
error(f.getFailure());
}
);
try {
result.getValue();
}
catch (Exception e) {
Throw.exception(e);
}
}
);
}
}
}