/*
* 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.karaf.shell.impl.action.osgi;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.felix.utils.extender.Extension;
import org.apache.felix.utils.manifest.Clause;
import org.apache.felix.utils.manifest.Parser;
import org.apache.karaf.shell.api.action.lifecycle.Manager;
import org.apache.karaf.shell.api.action.lifecycle.Reference;
import org.apache.karaf.shell.api.action.lifecycle.Service;
import org.apache.karaf.shell.api.console.History;
import org.apache.karaf.shell.api.console.Registry;
import org.apache.karaf.shell.api.console.Session;
import org.apache.karaf.shell.api.console.SessionFactory;
import org.apache.karaf.shell.api.console.Terminal;
import org.apache.karaf.shell.impl.action.command.ManagerImpl;
import org.apache.karaf.shell.support.converter.GenericType;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.wiring.BundleWiring;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Commands extension
*/
@SuppressWarnings("rawtypes")
public class CommandExtension implements Extension {
private static final Logger LOGGER = LoggerFactory.getLogger(CommandExtension.class);
private final Bundle bundle;
private final Registry registry;
private final CountDownLatch started;
private final AggregateServiceTracker tracker;
private final List<Class> classes = new ArrayList<Class>();
private Manager manager;
public CommandExtension(Bundle bundle, Registry registry) {
this.bundle = bundle;
this.registry = registry;
this.started = new CountDownLatch(1);
this.tracker = new AggregateServiceTracker(bundle.getBundleContext()) {
@Override
protected void updateState(State state) {
CommandExtension.this.updateState(state);
}
};
}
public void start() throws Exception {
try {
String header = bundle.getHeaders().get(CommandExtender.KARAF_COMMANDS);
Clause[] clauses = Parser.parseHeader(header);
BundleWiring wiring = bundle.adapt(BundleWiring.class);
for (Clause clause : clauses) {
String name = clause.getName();
int options = BundleWiring.LISTRESOURCES_LOCAL;
name = name.replace('.', '/');
if (name.endsWith("*")) {
options |= BundleWiring.LISTRESOURCES_RECURSE;
name = name.substring(0, name.length() - 1);
}
if (!name.startsWith("/")) {
name = "/" + name;
}
if (name.endsWith("/")) {
name = name.substring(0, name.length() - 1);
}
Collection<String> classes = wiring.listResources(name, "*.class", options);
for (String className : classes) {
className = className.replace('/', '.').replace(".class", "");
try {
inspectClass(bundle.loadClass(className));
} catch (final ClassNotFoundException | NoClassDefFoundError ex) {
LOGGER.info("Inspection of class {} failed.", className, ex);
}
}
}
AggregateServiceTracker.State state = tracker.open();
if (!state.isSatisfied()) {
LOGGER.info("Command registration delayed for bundle {}/{}. Missing dependencies: {}",
bundle.getSymbolicName(),
bundle.getVersion(),
state.getMissingServices());
} else {
updateState(state);
}
} finally {
started.countDown();
}
}
public void destroy() {
try {
started.await(5000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
LOGGER.warn("The wait for bundle being started before destruction has been interrupted.", e);
}
tracker.close();
}
@SuppressWarnings("unchecked")
private synchronized void updateState(AggregateServiceTracker.State state) {
boolean wasSatisfied = manager != null;
boolean isSatisfied = state != null && state.isSatisfied();
String action;
if (wasSatisfied && isSatisfied) {
action = "Updating";
} else if (wasSatisfied) {
action = "Unregistering";
} else if (isSatisfied) {
action = "Registering";
} else {
return;
}
LOGGER.info("{} commands for bundle {}/{}",
action,
bundle.getSymbolicName(),
bundle.getVersion());
if (wasSatisfied) {
for (Class clazz : classes) {
manager.unregister(clazz);
}
manager = null;
}
if (isSatisfied) {
Registry reg = new RegistryImpl(registry);
manager = new ManagerImpl(reg, registry);
reg.register(bundle.getBundleContext());
reg.register(manager);
for (Map.Entry<Class, Object> entry : state.getSingleServices().entrySet()) {
reg.register(entry.getValue());
}
for (final Map.Entry<Class, List> entry : state.getMultiServices().entrySet()) {
reg.register((Callable) entry::getValue, entry.getKey());
}
for (Class clazz : classes) {
manager.register(clazz);
}
}
}
private void inspectClass(final Class<?> clazz) throws Exception {
Service reg = clazz.getAnnotation(Service.class);
if (reg == null) {
return;
}
// Create trackers
for (Class<?> cl = clazz; cl != Object.class; cl = cl.getSuperclass()) {
for (Field field : cl.getDeclaredFields()) {
Reference ref = field.getAnnotation(Reference.class);
if (ref != null) {
GenericType type = new GenericType(field.getGenericType());
Class clazzRef = type.getRawClass() == List.class ? type.getActualTypeArgument(0).getRawClass() : type.getRawClass();
if (clazzRef != BundleContext.class
&& clazzRef != Session.class
&& clazzRef != Terminal.class
&& clazzRef != History.class
&& clazzRef != Registry.class
&& clazzRef != SessionFactory.class
&& !registry.hasService(clazzRef)) {
track(type, ref.optional());
}
}
}
}
classes.add(clazz);
}
@SuppressWarnings("unchecked")
protected void track(final GenericType type, boolean optional) {
if (type.getRawClass() == List.class) {
final Class clazzRef = type.getActualTypeArgument(0).getRawClass();
tracker.trackList(clazzRef);
} else {
final Class clazzRef = type.getRawClass();
tracker.trackSingle(clazzRef, optional);
}
}
}