blob: 06fd8f21ea4249d0d8595c0f8b499bfdf055b68f [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.gogo.command;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Formatter;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import org.apache.felix.service.command.CommandSession;
import org.apache.felix.service.command.Descriptor;
import org.apache.felix.service.command.Parameter;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.BundleReference;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.startlevel.BundleStartLevel;
import org.osgi.framework.startlevel.FrameworkStartLevel;
import org.osgi.framework.wiring.FrameworkWiring;
import org.osgi.service.log.LogEntry;
import org.osgi.service.log.LogReaderService;
import org.osgi.service.log.LogService;
public class Basic
{
private final BundleContext m_bc;
private final Bundle m_b0;
public Basic(BundleContext bc)
{
m_bc = bc;
m_b0 = m_bc.getBundle(0);
}
@Descriptor("query bundle start level")
public String bundlelevel(@Descriptor("bundle to query") Bundle bundle)
{
return bundle + " is level " + bundle.adapt(BundleStartLevel.class).getStartLevel();
}
@Descriptor("set bundle start level or initial bundle start level")
public String bundlelevel(
@Descriptor("set the bundle's start level") @Parameter(names = { "-s",
"--setlevel" }, presentValue = "true", absentValue = "false") boolean set,
@Descriptor("set the initial bundle start level") @Parameter(names = { "-i",
"--setinitial" }, presentValue = "true", absentValue = "false") boolean initial,
@Descriptor("target level") int level,
@Descriptor("target identifiers") Bundle[] bundles)
{
if (set && initial)
{
return "Cannot specify '-s' and '-i' at the same time.";
}
else if (!set && !initial)
{
return "Must specify either '-s' or '-i'.";
}
else if (level <= 0)
{
return "Specified start level must be greater than zero.";
}
// Set the initial bundle start level.
else if (initial)
{
if ((bundles != null) && (bundles.length == 0))
{
m_b0.adapt(FrameworkStartLevel.class).setInitialBundleStartLevel(level);
}
else
{
return "Cannot specify bundles when setting initial start level.";
}
}
// Set the bundle start level.
else if (set)
{
if ((bundles != null) && (bundles.length != 0))
{
for (Bundle bundle : bundles)
{
bundle.adapt(BundleStartLevel.class).setStartLevel(level);
}
}
else
{
return "Must specify target bundles.";
}
}
return null;
}
@Descriptor("query framework active start level")
public String frameworklevel()
{
return "Level is " + m_b0.adapt(FrameworkStartLevel.class).getStartLevel();
}
@Descriptor("set framework active start level")
public void frameworklevel(@Descriptor("target start level") int level)
{
m_b0.adapt(FrameworkStartLevel.class).setStartLevel(level);
}
@Descriptor("display bundle headers")
public String headers(@Descriptor("target bundles") Bundle[] bundles)
{
try (Formatter f = new Formatter()) {
bundles = ((bundles == null) || (bundles.length == 0)) ? m_bc.getBundles()
: bundles;
String prefix = "";
for (Bundle bundle : bundles)
{
String title = Util.getBundleName(bundle);
f.format("%s%s%n", prefix, title);
f.format("%s%n", Util.getUnderlineString(title.length()));
Dictionary<String, String> dict = bundle.getHeaders();
Enumeration<String> keys = dict.keys();
while (keys.hasMoreElements())
{
String k = keys.nextElement();
String v = dict.get(k);
f.format("%s = %s%n", k, v);
}
prefix = "\n";
}
return f.toString();
}
}
@Descriptor("displays available commands")
public String help()
{
try (Formatter f = new Formatter()) {
Map<String, List<Method>> commands = getCommands();
for (String name : commands.keySet())
{
f.format("%s%n", name);
}
return f.toString();
}
}
@Descriptor("displays information about a specific command")
public String help(@Descriptor("target command") String name)
{
Map<String, List<Method>> commands = getCommands();
List<Method> methods = null;
// If the specified command doesn't have a scope, then
// search for matching methods by ignoring the scope.
int scopeIdx = name.indexOf(':');
if (scopeIdx < 0)
{
for (Entry<String, List<Method>> entry : commands.entrySet())
{
String k = entry.getKey().substring(entry.getKey().indexOf(':') + 1);
if (name.equals(k))
{
name = entry.getKey();
methods = entry.getValue();
break;
}
}
}
// Otherwise directly look up matching methods.
else
{
methods = commands.get(name);
}
if ((methods == null) || (methods.size() <= 0))
{
return "No methods found matching: " + name;
}
try (Formatter f = new Formatter()) {
String prefix = "";
for (Method m : methods)
{
Descriptor d = m.getAnnotation(Descriptor.class);
if (d == null)
{
f.format("%s%s%n", prefix, m.getName());
}
else
{
f.format("%s%s - %s%n", prefix, m.getName(), d.value());
}
f.format(" scope: %s%n", name.substring(0, name.indexOf(':')));
// Get flags and options.
Class<?>[] paramTypes = m.getParameterTypes();
Map<String, Parameter> flags = new TreeMap<>();
Map<String, String> flagDescs = new TreeMap<>();
Map<String, Parameter> options = new TreeMap<>();
Map<String, String> optionDescs = new TreeMap<>();
List<String> params = new ArrayList<>();
Annotation[][] anns = m.getParameterAnnotations();
for (int paramIdx = 0; paramIdx < anns.length; paramIdx++)
{
Class<?> paramType = m.getParameterTypes()[paramIdx];
if (paramType == CommandSession.class) {
/* Do not bother the user with a CommandSession. */
continue;
}
Parameter p = findAnnotation(anns[paramIdx], Parameter.class);
d = findAnnotation(anns[paramIdx], Descriptor.class);
if (p != null)
{
if (p.presentValue().equals(Parameter.UNSPECIFIED))
{
options.put(p.names()[0], p);
if (d != null)
{
optionDescs.put(p.names()[0], d.value());
}
}
else
{
flags.put(p.names()[0], p);
if (d != null)
{
flagDescs.put(p.names()[0], d.value());
}
}
}
else if (d != null)
{
params.add(paramTypes[paramIdx].getSimpleName());
params.add(d.value());
}
else
{
params.add(paramTypes[paramIdx].getSimpleName());
params.add("");
}
}
// Print flags and options.
if (flags.size() > 0)
{
f.format(" flags:%n");
for (Entry<String, Parameter> entry : flags.entrySet())
{
// Print all aliases.
String[] names = entry.getValue().names();
f.format(" %s", names[0]);
for (int aliasIdx = 1; aliasIdx < names.length; aliasIdx++)
{
f.format(", %s", names[aliasIdx]);
}
f.format(" %s%n", flagDescs.get(entry.getKey()));
}
}
if (options.size() > 0)
{
f.format(" options:%n");
for (Entry<String, Parameter> entry : options.entrySet())
{
// Print all aliases.
String[] names = entry.getValue().names();
f.format(" %s", names[0]);
for (int aliasIdx = 1; aliasIdx < names.length; aliasIdx++)
{
f.format(", %s", names[aliasIdx]);
}
f.format(" %s%s%n",
optionDescs.get(entry.getKey()),
((entry.getValue().absentValue() == null) ? "" : " [optional]"));
}
}
if (params.size() > 0)
{
f.format(" parameters:%n");
for (Iterator<String> it = params.iterator(); it.hasNext();)
{
f.format(" %s %s%n", it.next(), it.next());
}
}
prefix = "\n";
}
return f.toString();
}
}
private static <T extends Annotation> T findAnnotation(Annotation[] anns,
Class<T> clazz)
{
for (int i = 0; (anns != null) && (i < anns.length); i++)
{
if (clazz.isInstance(anns[i]))
{
return clazz.cast(anns[i]);
}
}
return null;
}
private Map<String, List<Method>> getCommands()
{
ServiceReference<?>[] refs = null;
try
{
refs = m_bc.getAllServiceReferences(null, "(osgi.command.scope=*)");
}
catch (InvalidSyntaxException ex)
{
// This should never happen.
}
Map<String, List<Method>> commands = new TreeMap<>();
for (ServiceReference<?> ref : refs)
{
Object svc = m_bc.getService(ref);
if (svc != null)
{
String scope = (String) ref.getProperty("osgi.command.scope");
Object ofunc = ref.getProperty("osgi.command.function");
String[] funcs = (ofunc instanceof String[]) ? (String[]) ofunc
: new String[] { String.valueOf(ofunc) };
for (String func : funcs)
{
commands.put(scope + ":" + func, new ArrayList<Method>());
}
if (!commands.isEmpty())
{
Method[] methods = svc.getClass().getMethods();
for (Method method : methods)
{
List<Method> commandMethods = commands.get(scope + ":"
+ method.getName());
if (commandMethods != null)
{
commandMethods.add(method);
}
}
}
// Remove any missing commands.
Iterator<Entry<String, List<Method>>> it = commands.entrySet().iterator();
while (it.hasNext())
{
if (it.next().getValue().size() == 0)
{
it.remove();
}
}
}
}
return commands;
}
@Descriptor("install bundle using URLs")
public String install(@Descriptor("command session")CommandSession session,
@Descriptor("target URLs") String[] urls) throws IOException
{
try (Formatter f = new Formatter()) {
StringBuilder sb = new StringBuilder();
for (String url : urls)
{
String location = Util.resolveUri(session, url.trim());
Bundle bundle = null;
try
{
bundle = m_bc.installBundle(location, null);
}
catch (IllegalStateException ex)
{
f.format("%s%n", ex.toString());
}
catch (BundleException ex)
{
if (ex.getNestedException() != null)
{
f.format("%s%n", ex.getNestedException().toString());
}
else
{
f.format("%s%n", ex.toString());
}
}
catch (Exception ex)
{
f.format("%s%n", ex.toString());
}
if (bundle != null)
{
if (sb.length() > 0)
{
sb.append(", ");
}
sb.append(bundle.getBundleId());
}
}
if (sb.toString().indexOf(',') > 0)
{
return "Bundle IDs: " + sb.toString();
}
else if (sb.length() > 0)
{
return "Bundle ID: " + sb.toString();
}
return f.toString();
}
}
@Descriptor("list all installed bundles")
public String lb(
@Descriptor("show location") @Parameter(names = { "-l", "--location" }, presentValue = "true", absentValue = "false") boolean showLoc,
@Descriptor("show symbolic name") @Parameter(names = { "-s", "--symbolicname" }, presentValue = "true", absentValue = "false") boolean showSymbolic,
@Descriptor("show update location") @Parameter(names = { "-u", "--updatelocation" }, presentValue = "true", absentValue = "false") boolean showUpdate)
{
return lb(showLoc, showSymbolic, showUpdate, null);
}
@Descriptor("list installed bundles matching a substring")
public String lb(
@Descriptor("show location") @Parameter(names = { "-l", "--location" }, presentValue = "true", absentValue = "false") boolean showLoc,
@Descriptor("show symbolic name") @Parameter(names = { "-s", "--symbolicname" }, presentValue = "true", absentValue = "false") boolean showSymbolic,
@Descriptor("show update location") @Parameter(names = { "-u", "--updatelocation" }, presentValue = "true", absentValue = "false") boolean showUpdate,
@Descriptor("subtring matched against name or symbolic name") String pattern)
{
if ((showLoc && showSymbolic && showUpdate) ||
(showLoc && showSymbolic) ||
(showSymbolic && showUpdate) ||
(showLoc && showUpdate)) {
return "Only one of -l, -s, -u should be used.";
}
List<Bundle> found = new ArrayList<>();
if (pattern == null)
{
found.addAll(Arrays.asList(m_bc.getBundles()));
}
else
{
Bundle[] bundles = m_bc.getBundles();
for (Bundle bundle : bundles) {
String name = bundle.getHeaders().get(Constants.BUNDLE_NAME);
if (matchBundleName(bundle.getSymbolicName(), pattern)
|| matchBundleName(name, pattern)) {
found.add(bundle);
}
}
}
if (found.size() > 0)
{
try (Formatter f = new Formatter()) {
printBundleList(found.toArray(new Bundle[found.size()]),
showLoc, showSymbolic, showUpdate, m_b0, f);
return f.toString();
}
}
else
{
return "No matching bundles found.";
}
}
private boolean matchBundleName(String name, String pattern)
{
return (name != null) && name.toLowerCase().contains(pattern.toLowerCase());
}
@Descriptor("display all matching log entries")
public String log(
@Descriptor("minimum log level [ debug | info | warn | error ]") String logLevel)
{
return log(-1, logLevel);
}
@Descriptor("display some matching log entries")
public String log(@Descriptor("maximum number of entries") int maxEntries,
@Descriptor("minimum log level [ debug | info | warn | error ]") String logLevel)
{
// Keep track of service references.
List<ServiceReference<?>> refs = new ArrayList<>();
// Get start level service.
try {
LogReaderService lrs = Util.getService(m_bc, LogReaderService.class, refs);
if (lrs == null)
{
return "Log reader service is unavailable.";
}
else
{
try (Formatter f = new Formatter()) {
@SuppressWarnings("unchecked")
Enumeration<LogEntry> entries = lrs.getLog();
List<LogEntry> select = new ArrayList<>();
int minLevel = logLevelAsInt(logLevel);
int index = 0;
while (entries.hasMoreElements() && (maxEntries < 0 || index < maxEntries))
{
LogEntry entry = entries.nextElement();
if (entry.getLevel() <= minLevel)
{
select.add(0, entry);
index++;
}
}
for (LogEntry e : select) {
display(e,f);
}
Util.ungetServices(m_bc, refs);
return f.toString();
}
}
}
catch (NoClassDefFoundError ncdfe) {
return "Log reader service is unavailable.";
}
}
private void display(LogEntry entry, Formatter f)
{
SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
StringBuilder buffer = new StringBuilder();
buffer.append(sdf.format(new Date(entry.getTime()))).append(" ");
buffer.append(logLevelAsString(entry.getLevel())).append(" - ");
buffer.append("Bundle: ").append(entry.getBundle().getSymbolicName());
if (entry.getServiceReference() != null)
{
buffer.append(" - ");
buffer.append(entry.getServiceReference().toString());
}
buffer.append(" - ").append(entry.getMessage());
if (entry.getException() != null)
{
buffer.append(" - ");
StringWriter writer = new StringWriter();
PrintWriter pw = new PrintWriter(writer);
entry.getException().printStackTrace(pw);
buffer.append(writer.toString());
}
f.format("%s%n", buffer.toString());
}
private static int logLevelAsInt(String logLevel)
{
if ("error".equalsIgnoreCase(logLevel))
{
return LogService.LOG_ERROR;
}
else if ("warn".equalsIgnoreCase(logLevel))
{
return LogService.LOG_WARNING;
}
else if ("info".equalsIgnoreCase(logLevel))
{
return LogService.LOG_INFO;
}
return LogService.LOG_DEBUG;
}
private static String logLevelAsString(int level)
{
switch (level)
{
case LogService.LOG_ERROR:
return "ERROR";
case LogService.LOG_WARNING:
return "WARNING";
case LogService.LOG_INFO:
return "INFO";
default:
return "DEBUG";
}
}
@Descriptor("refresh bundles")
public void refresh(
@Descriptor("target bundles (can be null or empty)") Bundle[] bundles)
{
if ((bundles != null) && (bundles.length == 0))
{
bundles = null;
}
m_b0.adapt(FrameworkWiring.class).refreshBundles(Arrays.asList(bundles));
}
@Descriptor("resolve bundles")
public String resolve(
@Descriptor("target bundles (can be null or empty)") Bundle[] bundles)
{
if (m_b0.adapt(FrameworkWiring.class).resolveBundles(bundles != null ? Arrays.asList(bundles) : null))
{
return "Not all bundles could be resolved.";
}
return null;
}
@Descriptor("start bundles")
public String start(
@Descriptor("start bundle transiently") @Parameter(names = { "-t", "--transient" }, presentValue = "true", absentValue = "false") boolean trans,
@Descriptor("use declared activation policy") @Parameter(names = { "-p",
"--policy" }, presentValue = "true", absentValue = "false") boolean policy,
@Descriptor("target bundle identifiers or URLs") String[] ss)
{
if ((ss == null) || (ss.length < 1)) {
return "Please specify the bundles to start.";
}
int options = 0;
// Check for "transient" switch.
if (trans)
{
options |= Bundle.START_TRANSIENT;
}
// Check for "start policy" switch.
if (policy)
{
options |= Bundle.START_ACTIVATION_POLICY;
}
try (Formatter f = new Formatter()) {
for (String s : ss)
{
String id = s.trim();
try
{
Bundle bundle = null;
// The id may be a number or a URL, so check.
if (Character.isDigit(id.charAt(0)))
{
long l = Long.parseLong(id);
bundle = m_bc.getBundle(l);
}
else
{
bundle = m_bc.installBundle(id);
}
if (bundle != null)
{
bundle.start(options);
}
else
{
f.format("Bundle ID '%s' is invalid.%n", id);
}
}
catch (NumberFormatException ex)
{
f.format("Unable to parse id '%s'.%n", id);
}
catch (BundleException ex)
{
if (ex.getNestedException() != null)
{
f.format("%s%n", ex.getNestedException().toString());
}
else
{
f.format("%s%n", ex.toString());
}
}
catch (Exception ex)
{
f.format("%s%n", ex.toString());
}
}
return f.toString();
}
}
@Descriptor("stop bundles")
public String stop(@Descriptor("stop bundle transiently") @Parameter(names = { "-t",
"--transient" }, presentValue = "true", absentValue = "false") boolean trans,
@Descriptor("target bundles") Bundle[] bundles)
{
if ((bundles == null) || (bundles.length == 0))
{
return "Please specify the bundles to stop.";
}
int options = 0;
// Check for "transient" switch.
if (trans)
{
options |= Bundle.STOP_TRANSIENT;
}
try (Formatter f = new Formatter()) {
for (Bundle bundle : bundles)
{
try
{
bundle.stop(options);
}
catch (BundleException ex)
{
if (ex.getNestedException() != null)
{
f.format("%s%n", ex.getNestedException().toString());
}
else
{
f.format("%s%n", ex.toString());
}
}
catch (Exception ex)
{
f.format("%s%n", ex.toString());
}
}
return f.toString();
}
}
@Descriptor("uninstall bundles")
public String uninstall(@Descriptor("target bundles") Bundle[] bundles)
{
if ((bundles == null) || (bundles.length == 0))
{
return "Please specify the bundles to uninstall.";
}
try (Formatter f = new Formatter()) {
for (Bundle bundle : bundles)
{
try
{
bundle.uninstall();
}
catch (BundleException ex)
{
if (ex.getNestedException() != null)
{
f.format("%s%n", ex.getNestedException().toString());
}
else {
f.format("%s%n", ex.toString());
}
}
catch (Exception ex)
{
f.format("%s%n", ex.toString());
}
}
return f.toString();
}
}
@Descriptor("update bundle")
public String update(@Descriptor("target bundle") Bundle bundle)
{
if (bundle == null)
{
return "Must specify a bundle.";
}
try
{
bundle.update();
return null;
}
catch (BundleException ex)
{
if (ex.getNestedException() != null)
{
return ex.getNestedException().toString();
}
return ex.toString();
}
catch (Exception ex)
{
return ex.toString();
}
}
@Descriptor("update bundle from URL")
public String update(
@Descriptor("command session") CommandSession session,
@Descriptor("target bundle") Bundle bundle,
@Descriptor("URL from where to retrieve bundle") String location) throws IOException {
if (bundle == null)
{
return "Must specify a bundle.";
}
if (location == null)
{
return "Must specify a location.";
}
try
{
location = Util.resolveUri(session, location.trim());
InputStream is = new URL(location).openStream();
bundle.update(is);
return null;
}
catch (MalformedURLException ex)
{
return "Unable to parse URL";
}
catch (IOException ex)
{
return "Unable to open input stream: " + ex;
}
catch (BundleException ex)
{
if (ex.getNestedException() != null)
{
return ex.getNestedException().toString();
}
return ex.toString();
}
catch (Exception ex)
{
return ex.toString();
}
}
@Descriptor("determines from where a bundle loads a class")
public String which(@Descriptor("target bundle") Bundle bundle,
@Descriptor("target class name") String className)
{
if (bundle == null)
{
return "Please specify a bundle";
}
Class<?> clazz = null;
try
{
clazz = bundle.loadClass(className);
if (clazz.getClassLoader() == null)
{
return "Loaded from: boot class loader";
}
else if (clazz.getClassLoader() instanceof BundleReference)
{
Bundle p = ((BundleReference) clazz.getClassLoader()).getBundle();
return "Loaded from: " + p;
}
else
{
return "Loaded from: " + clazz.getClassLoader();
}
}
catch (ClassNotFoundException ex)
{
return "Class not found";
}
}
private static void printBundleList(Bundle[] bundles,
boolean showLoc, boolean showSymbolic, boolean showUpdate, Bundle b0, Formatter f)
{
f.format("START LEVEL %s%n", b0.adapt(FrameworkStartLevel.class).getStartLevel());
// Determine last column.
String lastColumn = "Name";
if (showLoc)
{
lastColumn = "Location";
}
else if (showSymbolic)
{
lastColumn = "Symbolic name";
}
else if (showUpdate)
{
lastColumn = "Update location";
}
f.format("%5s|%-11s|%5s|%s%n", "ID", "State", "Level", lastColumn);
for (Bundle bundle : bundles)
{
// Get the bundle name or location.
String name = bundle.getHeaders().get(Constants.BUNDLE_NAME);
// If there is no name, then default to symbolic name.
name = (name == null) ? bundle.getSymbolicName() : name;
// If there is no symbolic name, resort to location.
name = (name == null) ? bundle.getLocation() : name;
// Overwrite the default value is the user specifically
// requested to display one or the other.
if (showLoc)
{
name = bundle.getLocation();
}
else if (showSymbolic)
{
name = bundle.getSymbolicName();
name = (name == null) ? "<no symbolic name>" : name;
}
else if (showUpdate)
{
name = bundle.getHeaders().get(Constants.BUNDLE_UPDATELOCATION);
name = (name == null) ? bundle.getLocation() : name;
}
// Show bundle version if not showing location.
name = (!showLoc && !showUpdate) ? name + " (" + bundle.getVersion() + ")"
: name;
// Get the bundle's start level.
int level = bundle.adapt(BundleStartLevel.class).getStartLevel();
f.format("%5d|%-11s|%5d|%s|%s%n",
bundle.getBundleId(), getStateString(bundle), level, name,
bundle.getVersion());
}
}
private static String getStateString(Bundle bundle)
{
int state = bundle.getState();
if (state == Bundle.ACTIVE)
{
return "Active ";
}
else if (state == Bundle.INSTALLED)
{
return "Installed ";
}
else if (state == Bundle.RESOLVED)
{
return "Resolved ";
}
else if (state == Bundle.STARTING)
{
return "Starting ";
}
else if (state == Bundle.STOPPING)
{
return "Stopping ";
}
else
{
return "Unknown ";
}
}
}