blob: e48521125584f190f4e278ce8c82d209761a108f [file] [log] [blame]
/*
* 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.scr.impl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.felix.scr.impl.manager.ScrConfiguration;
import org.apache.felix.scr.info.ScrInfo;
import org.apache.felix.service.command.Converter;
import org.apache.felix.service.command.Descriptor;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.dto.ServiceReferenceDTO;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.BundleWiring;
import org.osgi.service.component.ComponentConstants;
import org.osgi.service.component.ComponentFactory;
import org.osgi.service.component.runtime.ServiceComponentRuntime;
import org.osgi.service.component.runtime.dto.ComponentConfigurationDTO;
import org.osgi.service.component.runtime.dto.ComponentDescriptionDTO;
import org.osgi.service.component.runtime.dto.ReferenceDTO;
import org.osgi.service.component.runtime.dto.SatisfiedReferenceDTO;
import org.osgi.service.component.runtime.dto.UnsatisfiedReferenceDTO;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
public class ComponentCommands implements ServiceTrackerCustomizer<Object, ServiceRegistration<?>> {
private static final String INDENT_1 = " ";
private static final String INDENT_2 = INDENT_1 + INDENT_1;
private final BundleContext context;
private final ServiceComponentRuntime scr;
private final ScrConfiguration scrConfig;
private final ServiceTracker<Object, ServiceRegistration<?>> gogoRuntimeTracker;
private final Comparator<ComponentConfigurationDTO> configDtoComparator = new Comparator<ComponentConfigurationDTO>() {
@Override
public int compare(ComponentConfigurationDTO o1, ComponentConfigurationDTO o2) {
long diff = o1.id - o2.id;
return diff == 0L ? 0 : (int) (diff / Math.abs(diff));
}
};
private final Comparator<ServiceReferenceDTO> serviceRefDtoComparator = new Comparator<ServiceReferenceDTO>() {
@Override
public int compare(ServiceReferenceDTO o1, ServiceReferenceDTO o2) {
long diff = o1.id - o2.id;
return diff == 0L ? 0 : (int) (diff / Math.abs(diff));
}
};
private ServiceRegistration<ComponentCommands> commandsReg = null;
private ServiceRegistration<ScrInfo> scrInfoReg = null;
synchronized void register() {
if (commandsReg != null) {
throw new IllegalStateException("Component Commands already registered");
}
Dictionary<String, Object> svcProps;
svcProps = new Hashtable<>();
svcProps.put("osgi.command.scope", "scr");
svcProps.put("osgi.command.function", new String[] {
"config",
"disable",
"enable",
"info",
"list"
});
svcProps.put(Constants.SERVICE_DESCRIPTION, "SCR Gogo Shell Support");
svcProps.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
commandsReg = context.registerService(ComponentCommands.class, this, svcProps);
gogoRuntimeTracker.open(true);
}
synchronized void unregister() {
gogoRuntimeTracker.close();
safeUnregister(commandsReg);
safeUnregister(scrInfoReg);
}
public synchronized void updateProvideScrInfoService(boolean register) {
if (register) {
if (scrInfoReg == null) {
Dictionary<String, Object> svcProps = new Hashtable<>();
svcProps.put(Constants.SERVICE_DESCRIPTION, "SCR Info Service");
svcProps.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
scrInfoReg = context.registerService(ScrInfo.class, new ComponentCommandsScrInfo(this, context), svcProps);
}
} else {
safeUnregister(scrInfoReg);
scrInfoReg = null;
}
}
protected ComponentCommands(BundleContext context, ServiceComponentRuntime scr, ScrConfiguration scrConfig) {
this.context = context;
this.scr = scr;
this.scrConfig = scrConfig;
this.gogoRuntimeTracker = new ServiceTracker<>(context, "org.apache.felix.service.command.CommandProcessor", this);
}
@Descriptor("List all components")
public ComponentDescriptionDTO[] list() {
ComponentDescriptionDTO[] result = scr.getComponentDescriptionDTOs().toArray(new ComponentDescriptionDTO[0]);
Arrays.sort(result, new Comparator<ComponentDescriptionDTO>() {
@Override
public int compare(ComponentDescriptionDTO c1, ComponentDescriptionDTO c2) {
return Long.compare(c1.bundle.id, c2.bundle.id);
}
});
return result;
}
@Descriptor("List components of a specific bundle")
public ComponentDescriptionDTO[] list(@Descriptor("ID of the bundle") long bundleId) {
Bundle bundle = context.getBundle(bundleId);
return bundle != null ? scr.getComponentDescriptionDTOs(bundle).toArray(new ComponentDescriptionDTO[0]) : null;
}
private List<ComponentDescriptionDTO> findComponents(String name) {
String lowerName = name.toLowerCase();
List<ComponentDescriptionDTO> matches = new LinkedList<>();
for (ComponentDescriptionDTO dto : scr.getComponentDescriptionDTOs()) {
if (dto.name.equalsIgnoreCase(name)) {
// Exact match, return only this component.
return Collections.singletonList(dto);
}
if (dto.name.toLowerCase().contains(lowerName))
matches.add(dto);
}
return matches;
}
@Descriptor("Dump information of a component")
public ComponentDescriptionDTO info(@Descriptor("Name of the component") String name) {
List<ComponentDescriptionDTO> matches = findComponents(name);
if (matches.isEmpty()) {
throw new IllegalArgumentException(MessageFormat.format("No component description matching \"{0}\".", name));
} else if (matches.size() > 1) {
StringBuilder partialMatchesStr = new StringBuilder();
for (Iterator<ComponentDescriptionDTO> iter = matches.iterator(); iter.hasNext(); ) {
partialMatchesStr.append(iter.next().name);
if (iter.hasNext()) partialMatchesStr.append(", ");
}
throw new IllegalArgumentException(MessageFormat.format("Multiple components matching \"{0}\": [{1}]", name, partialMatchesStr));
}
return matches.get(0);
}
@Descriptor("Dump information of a component configuration")
public ComponentConfigurationDTO info(@Descriptor("ID of the component configuration") long id) {
for (ComponentDescriptionDTO descDto : scr.getComponentDescriptionDTOs()) {
for (ComponentConfigurationDTO configDto : scr.getComponentConfigurationDTOs(descDto)) {
if (configDto.id == id)
return configDto;
}
}
return null;
}
@Descriptor("Enable a disabled component")
public boolean enable(@Descriptor("Name of the component") final String name) {
boolean changed = false;
for (ComponentDescriptionDTO comp : findComponents(name)) {
if (!scr.isComponentEnabled(comp)) {
scr.enableComponent(comp);
changed = true;
}
}
return changed;
}
@Descriptor("Disable an enabled component")
public boolean disable(@Descriptor("Name of the component") final String name) {
boolean changed = false;
for (ComponentDescriptionDTO comp : findComponents(name)) {
if (scr.isComponentEnabled(comp)) {
scr.disableComponent(comp);
changed = true;
}
}
return changed;
}
@SuppressWarnings("deprecation")
@Descriptor("Show the current SCR configuration")
public String config() {
Map<String,String> out = new LinkedHashMap<>();
out.put("Log Level", Integer.toString(scrConfig.getLogLevel()));
out.put("Obsolete Component Factory with Factory Configuration", Boolean.toString(scrConfig.isFactoryEnabled()));
out.put("Keep instances with no references", scrConfig.keepInstances() ? "Supported" : "Unsupported");
out.put("Lock timeout ms", Long.toString(scrConfig.lockTimeout()));
out.put("Stop timeout ms", Long.toString(scrConfig.stopTimeout()));
out.put("Global extender", Boolean.toString(scrConfig.globalExtender()));
out.put("Info Service registered", scrConfig.infoAsService() ? "Supported" : "Unsupported");
StringBuilder builder = new StringBuilder();
printColumnsAligned("SCR Configuration", out, '=', builder);
return builder.toString();
}
public Object convert(Class<?> desiredType, Object in) throws Exception {
return null;
}
public CharSequence format(Object target, int level) throws Exception {
final CharSequence result;
if (target instanceof ComponentDescriptionDTO[]) {
result = format((ComponentDescriptionDTO[]) target, level);
} else if (target instanceof ComponentDescriptionDTO) {
result = format((ComponentDescriptionDTO) target, level);
} else if (target instanceof ComponentConfigurationDTO) {
result = format((ComponentConfigurationDTO) target, level);
} else {
result = null;
}
return result;
}
CharSequence format(ComponentDescriptionDTO[] dtoArray, int level) throws Exception {
StringBuilder sb = new StringBuilder();
if (dtoArray == null || dtoArray.length == 0) {
sb.append("No component descriptions found");
} else {
for (int i = 0; i < dtoArray.length; i++) {
if (i > 0) sb.append('\n');
sb.append(format(dtoArray[i], Converter.LINE));
}
}
return sb;
}
CharSequence format(ComponentDescriptionDTO dto, int level) throws Exception {
final StringBuilder builder = new StringBuilder();
// Get the child ComponentConfigurationDTOs and sort by id field.
final List<ComponentConfigurationDTO> children;
Collection<ComponentConfigurationDTO> childrenTmp = scr.getComponentConfigurationDTOs(dto);
if (childrenTmp == null) {
children = Collections.emptyList();
} else {
children = new ArrayList<>(childrenTmp);
Collections.sort(children, configDtoComparator);
}
switch (level) {
case Converter.LINE:
builder.append(MessageFormat.format("{0} in bundle {1} ({2}:{3}) {4}, {5,choice,0#0 instances|1#1 instance|1<{5} instances}.",
dto.name,
dto.bundle.id,
dto.bundle.symbolicName,
dto.bundle.version,
dto.defaultEnabled ? "enabled" : "disabled",
children.size()
));
for (ComponentConfigurationDTO child : children)
builder.append("\n").append(INDENT_2).append(format(child, Converter.LINE));
break;
case Converter.INSPECT:
printComponentDescriptionAndConfigs(dto, children.toArray(new ComponentConfigurationDTO[0]), builder);
break;
case Converter.PART:
break;
}
return builder;
}
CharSequence format(ComponentConfigurationDTO dto, int level) throws Exception {
final StringBuilder builder = new StringBuilder();
switch (level) {
case Converter.INSPECT:
printComponentDescriptionAndConfigs(dto.description, new ComponentConfigurationDTO[]{dto}, builder);
break;
case Converter.LINE:
builder.append("Id: ").append(dto.id);
builder.append(", ").append("State:").append(stateToString(dto.state));
String[] pids = getStringArray(dto.properties, Constants.SERVICE_PID, null);
if (pids != null && pids.length > 0) {
builder.append(", ").append("PID(s): ").append(Arrays.toString(pids));
}
break;
case Converter.PART:
break;
}
return builder;
}
void printComponentDescriptionAndConfigs(ComponentDescriptionDTO descDto, ComponentConfigurationDTO[] configs, StringBuilder builder) {
final Map<String, String> out = new LinkedHashMap<>();
// Component Description
out.put("Class", descDto.implementationClass);
out.put("Bundle", String.format("%d (%s:%s)", descDto.bundle.id, descDto.bundle.symbolicName, descDto.bundle.version));
out.put("Enabled", Boolean.toString(descDto.defaultEnabled));
out.put("Immediate", Boolean.toString(descDto.immediate));
out.put("Services", arrayToString(descDto.serviceInterfaces));
if (descDto.scope != null) {
out.put("Scope", descDto.scope);
}
out.put("Config PID(s)", String.format("%s, Policy: %s", arrayToString(descDto.configurationPid), descDto.configurationPolicy));
out.put("Base Props", printProperties(descDto.properties, INDENT_1));
if (descDto.factory != null) {
out.put("Factory", descDto.factory);
try {
ServiceReference<?>[] serviceRefs = context.getAllServiceReferences(ComponentFactory.class.getName(), String.format("(&(%s=%s)(%s=%d))", ComponentConstants.COMPONENT_NAME, descDto.name, Constants.SERVICE_BUNDLEID, descDto.bundle.id));
if (serviceRefs != null && serviceRefs.length > 0) {
out.put("Factory Service", printPublishedServices(serviceRefs));
}
} catch (InvalidSyntaxException e) {
// shouldn't happen
}
}
printColumnsAligned(String.format("Component Description: %s", descDto.name), out, '=', builder);
if (configs != null) for (ComponentConfigurationDTO configDto : configs) {
out.clear();
// Blank line separator
builder.append("\n\n");
// Inspect configuration DTO
String title = String.format("Component Configuration Id: %d", configDto.id);
out.put("State", stateToString(configDto.state));
// Print service registration
try {
ServiceReference<?>[] serviceRefs = context.getAllServiceReferences(null, String.format("(%s=%d)", ComponentConstants.COMPONENT_ID, configDto.id));
if (serviceRefs != null && serviceRefs.length > 0) {
out.put("Service", printPublishedServices(serviceRefs));
}
} catch (InvalidSyntaxException e) {
// Shouldn't happen...
}
// Print Configuration Properties
out.put("Config Props", printProperties(configDto.properties, INDENT_1));
// Print References
out.put("References", printServiceReferences(configDto.satisfiedReferences, configDto.unsatisfiedReferences, descDto.references));
// Print Failure
if (configDto.failure != null) {
out.put("Failure", configDto.failure);
}
printColumnsAligned(title, out, '-', builder);
}
}
String printPublishedServices(ServiceReference<?>[] serviceRefs) {
StringBuilder sb = new StringBuilder();
if (serviceRefs.length > 1) {
sb.append("(total ").append(serviceRefs.length).append(')');
sb.append('\n').append(INDENT_1);
}
for (ServiceReference<?> serviceRef : serviceRefs) {
sb.append(serviceRef.getProperty(Constants.SERVICE_ID));
sb.append(' ').append(Arrays.toString((String[]) serviceRef.getProperty(Constants.OBJECTCLASS)));
Bundle[] consumers = serviceRef.getUsingBundles();
if (consumers != null) for (Bundle consumer : consumers) {
sb.append("\n").append(INDENT_2);
sb.append(String.format("Used by bundle %d (%s:%s)", consumer.getBundleId(), consumer.getSymbolicName(), consumer.getVersion()));
}
}
return sb.toString();
}
private String arrayToString(String[] array) {
return array == null || array.length == 0 ? "<<none>>" : Arrays.toString(array);
}
static final String stateToString(int state) {
final String string;
switch (state) {
case ComponentConfigurationDTO.ACTIVE:
string = "ACTIVE";
break;
case ComponentConfigurationDTO.SATISFIED:
string = "SATISFIED";
break;
case ComponentConfigurationDTO.UNSATISFIED_CONFIGURATION:
string = "UNSATISFIED CONFIGURATION";
break;
case ComponentConfigurationDTO.UNSATISFIED_REFERENCE:
string = "UNSATISFIED REFERENCE";
break;
case ComponentConfigurationDTO.FAILED_ACTIVATION:
string = "FAILED ACTIVATION";
break;
default:
string = String.format("<<UNKNOWN: %d>>", state);
}
return string;
}
static String printProperties(Map<String, ?> props, String indent) {
StringBuilder builder = new StringBuilder();
int size = props.size();
builder.append('(').append(Integer.toString(size)).append(' ').append(size == 1 ? "entry" : "entries").append(')');
if (size > 0) {
final SortedMap<String, ?> sortedMap = new TreeMap<>(props);
for (Map.Entry<String, ?> e : sortedMap.entrySet()) {
builder.append('\n').append(indent);
final Object value = e.getValue();
final String typeName;
final String valueStr;
if (value == null) {
typeName = valueStr = "null";
} else {
typeName = value.getClass().getSimpleName();
if (value instanceof int[])
valueStr = Arrays.toString((int[]) value);
else if (value instanceof long[])
valueStr = Arrays.toString((long[]) value);
else if (value instanceof byte[])
valueStr = Arrays.toString((byte[]) value);
else if (value instanceof short[])
valueStr = Arrays.toString((short[]) value);
else if (value instanceof byte[])
valueStr = Arrays.toString((byte[]) value);
else if (value instanceof char[])
valueStr = Arrays.toString((char[]) value);
else if (value instanceof boolean[])
valueStr = Arrays.toString((boolean[]) value);
else if (value instanceof float[])
valueStr = Arrays.toString((boolean[]) value);
else if (value instanceof double[])
valueStr = Arrays.toString((boolean[]) value);
else if (value instanceof Object[])
valueStr = Arrays.deepToString((Object[]) value);
else
valueStr = value.toString();
}
builder.append(String.format("%s<%s> = %s", e.getKey(), typeName, valueStr));
}
}
return builder.toString();
}
String printServiceReferences(SatisfiedReferenceDTO[] satisfiedReferences, UnsatisfiedReferenceDTO[] unsatisfiedReferences, ReferenceDTO[] references) {
StringBuilder builder = new StringBuilder();
final Map<String, ReferenceDTO> refDtoMap = new HashMap<>();
if (references != null) {
for (ReferenceDTO refDto : references)
refDtoMap.put(refDto.name, refDto);
}
int refCount = (satisfiedReferences != null ? satisfiedReferences.length : 0)
+ (unsatisfiedReferences != null ? unsatisfiedReferences.length : 0);
builder.append("(total ").append(Integer.toString(refCount)).append(")");
if (unsatisfiedReferences != null) {
for (UnsatisfiedReferenceDTO refDto : unsatisfiedReferences)
printServiceReference(refDtoMap.get(refDto.name), "UNSATISFIED", null, builder);
}
if (satisfiedReferences != null) {
for (SatisfiedReferenceDTO refDto : satisfiedReferences)
printServiceReference(refDtoMap.get(refDto.name), "SATISFIED", refDto.boundServices != null ? refDto.boundServices : new ServiceReferenceDTO[0], builder);
}
return builder.toString();
}
void printServiceReference(ReferenceDTO reference, String state, ServiceReferenceDTO[] bindings, StringBuilder builder) {
StringBuilder policyWithOption = new StringBuilder().append(reference.policy);
if (!"reluctant".equals(reference.policyOption))
policyWithOption.append('+').append(reference.policyOption);
builder.append(String.format("%n" + INDENT_1 + "- %s: %s %s %s %s", reference.name, reference.interfaceName, state, reference.cardinality, policyWithOption));
builder.append(String.format("%n" + INDENT_1 + " target=%s scope=%s", reference.target == null ? "(*)" : reference.target, reference.scope == null ? "bundle" : reference.scope));
if (reference.collectionType != null) {
builder.append(" collectionType=").append(reference.collectionType);
}
if (bindings != null) {
Arrays.sort(bindings, serviceRefDtoComparator);
builder.append(MessageFormat.format(" {0,choice,0#(no active bindings)|1#(1 binding):|1<({0} bindings):}", bindings.length));
for (ServiceReferenceDTO svcDto : bindings) {
Bundle provider = context.getBundle(svcDto.bundle);
builder.append(String.format("%n" + INDENT_1 + " * Bound to [%d] from bundle %d (%s:%s)", svcDto.id, svcDto.bundle, provider.getSymbolicName(), provider.getVersion()));
}
}
}
static void printColumnsAligned(String title, Map<String, String> properties, char underlineChar, StringBuilder builder) {
builder.append(title);
// Generate the title underline
char[] carray = new char[title.length()];
Arrays.fill(carray, underlineChar);
builder.append('\n');
builder.append(carray);
int widestKey = 0;
for (String key : properties.keySet()) {
widestKey = Math.max(widestKey, key.length());
}
for (Map.Entry<String, String> e : properties.entrySet()) {
String key = e.getKey();
int padLength = widestKey - key.length();
char[] padding = new char[padLength];
Arrays.fill(padding, ' ');
builder.append('\n');
builder.append(key).append(": ");
builder.append(padding);
builder.append(e.getValue());
}
}
private static String[] getStringArray(Map<String, ?> map, String name, String[] defaultValue) throws IllegalArgumentException {
Object o = map.get(name);
if (o instanceof String) {
return new String[]{(String) o};
} else if (o instanceof String[]) {
return (String[]) o;
} else if (o instanceof Collection) {
Collection<?> c = (Collection<?>) o;
if (c.isEmpty()) {
return new String[0];
} else {
String[] a = new String[c.size()];
Iterator<?> iter = c.iterator();
for (int i = 0; i < a.length; i++) {
Object elem = iter.next();
if (!(elem instanceof String))
throw new IllegalArgumentException(String.format("Collection value for field '%s' contains non-String element at index %d.", name, i));
a[i] = (String) elem;
}
return a;
}
} else if (o == null) {
return defaultValue;
} else {
throw new IllegalArgumentException(String.format("Value for field '%s' is not a String, String-array or Collection of String. Actual type was %s.", name, o.getClass().getName()));
}
}
private void safeUnregister(ServiceRegistration<?> registration) {
try {
if (registration != null) registration.unregister();
} catch (Exception e) {
// ignore
}
}
@Override
public ServiceRegistration<?> addingService(ServiceReference<Object> reference) {
Bundle b = reference.getBundle();
BundleRevision rev = b == null ? null : b.adapt(BundleRevision.class);
if (rev != null) {
Object converter = createConverter(b);
if (converter != null) {
Dictionary<String, Object> svcProps = new Hashtable<>();
svcProps.put("osgi.converter.classes", new String[] {
ComponentDescriptionDTO.class.getName(),
ComponentConfigurationDTO.class.getName()
});
svcProps.put(Constants.SERVICE_DESCRIPTION, "SCR Runtime DTO Converter");
svcProps.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
return b.getBundleContext().registerService("org.apache.felix.service.command.Converter", converter, svcProps);
}
}
return null;
}
private Object createConverter(Bundle bundle) {
BundleWiring wiring = bundle.adapt(BundleWiring.class);
if (wiring != null) {
ClassLoader cl = wiring.getClassLoader();
if (cl != null) {
try {
Class<?>[] types = new Class[] {cl.loadClass("org.apache.felix.service.command.Converter")};
return Proxy.newProxyInstance(cl, types, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("convert".equals(method.getName())) {
return convert((Class<?>) args[0], args[1]);
}
if ("format".equals(method.getName())) {
return format(args[0], (int) args[1]);
}
return method.invoke(this, args);
}
});
} catch (ClassNotFoundException e) {
}
}
}
return null;
}
@Override
public void modifiedService(ServiceReference<Object> reference, ServiceRegistration<?> reg) {
// nothing
}
@Override
public void removedService(ServiceReference<Object> reference, ServiceRegistration<?> reg) {
safeUnregister(reg);
}
}