public class OsgiExplorer {

    public static final String BUNDLE_STORAGE_PROP = "org.osgi.framework.storage";

    public static final String BUNDLE_STORAGE_DIR_DEFAULT = "felix-cache";

    public static final String BUNDLE_PATH_FORMAT = "%s/bundle%s";

    public static final String BUNDLE_JAR_FILE = "bundle.jar";

    public static final String JAR_EXT = "jar";

    private static final Logger LOG = LoggerFactory.getLogger(OsgiExplorer.class);

    private final MetaTypeService metaTypeService;

    private final ConfigurationAdmin configAdmin;

    private BundleContext context;

    public OsgiExplorer(BundleContext context) {
        this.context = context;
        this.metaTypeService = service(MetaTypeService.class);
        this.configAdmin = service(ConfigurationAdmin.class);
    }

    public File findDir(Long bundleId) {
        return new File(String.format(BUNDLE_PATH_FORMAT, getBundleDir(), bundleId));
    }

    private String getBundleDir() {
        return StringUtils.defaultIfBlank(context.getProperty(BUNDLE_STORAGE_PROP),
                System.getProperty("user.dir") + "/" + BUNDLE_STORAGE_DIR_DEFAULT);
    }

    public File findJar(String bundleId) {
        return findJar(Long.valueOf(bundleId));
    }

    public File findJar(Long bundleId) {
        return findJar(findDir(bundleId));
    }

    public File findJar(File bundleDir) {
        if (bundleDir.exists()) {
            List<File> files = FluentIterable.from(FileUtils.listFiles(bundleDir, new String[]{JAR_EXT}, true)).filter(new Predicate<File>() {
                @Override
                public boolean apply(File file) {
                    return file.getName().equalsIgnoreCase(BUNDLE_JAR_FILE);
                }
            }).toSortedList(new Comparator<File>() {
                @Override
                public int compare(File f1, File f2) {
                    return f2.getAbsolutePath().compareTo(f1.getAbsolutePath());
                }
            });

            return Iterables.getFirst(files, null);
        }

        return null;
    }

    public BundleJar findBundleJar(String bundleId) {
        BundleJar result = null;

        File jar = findJar(bundleId);
        Bundle bundle = findBundle(bundleId);

        if (jar != null && bundle != null) {
            result = new BundleJar(bundle, jar);
        }

        return result;
    }

    public Set<BundleJar> findBundleJars(List<String> bundleIds) {
        Set<BundleJar> result = Sets.newLinkedHashSet();

        for (String bundleId : bundleIds) {
            BundleJar jar = findBundleJar(bundleId);
            if (jar != null) {
                result.add(jar);
            }
        }

        return result;
    }

    public String proposeJarName(Long bundleId) {
        return proposeJarName(String.valueOf(bundleId));
    }

    public String proposeJarName(String bundleId) {
        String result = BUNDLE_JAR_FILE;

        final Bundle bundle = context.getBundle(Long.valueOf(bundleId));
        if (bundle != null) {
            final String realName = FilenameUtils.getName(bundle.getLocation());
            if (StringUtils.isNotEmpty(realName) && JAR_EXT.equalsIgnoreCase(FilenameUtils.getExtension(realName))) {
                result = realName;
            } else {
                result = String.format("%s.%s", bundle.getSymbolicName(), JAR_EXT);
            }
        }

        return result;
    }

    public String decompileClass(BundleClass clazz) {
        return decompileClass(clazz.getBundle().getBundleId(), clazz.getClassName());
    }

    public String decompileClass(Long bundleId, String className) {
        return decompileClass(findJar(bundleId), className);
    }

    public String decompileClass(File jar, String className) {
        String source = StringUtils.EMPTY;
        String path = StringUtils.replace(className, ".", "/");

        try {
            final ByteArrayOutputStream stream = new ByteArrayOutputStream();
            try {
                try (OutputStreamWriter writer = new OutputStreamWriter(stream)) {
                    DecompilerSettings settings = DecompilerSettings.javaDefaults();
                    settings.setTypeLoader(new JarTypeLoader(new JarFile(jar)));
                    settings.setForceExplicitImports(true);
                    settings.setForceExplicitTypeArguments(true);

                    Decompiler.decompile(path, new PlainTextOutput(writer), settings);
                    stream.flush();
                }
            } finally {
                stream.close();
                source = new String(stream.toByteArray(), Charset.defaultCharset());
            }
        } catch (final IOException e) {
            // handle error
        }

        return source;
    }

    public List<BundleClass> findClasses(final Bundle bundle) {
        return FluentIterable.from(new BundleScanner(bundle).findClassNames()).transform(new Function<String, BundleClass>() {
            @Override
            public BundleClass apply(String className) {
                return new BundleClass(bundle, className);
            }
        }).toList();
    }

    public Iterable<BundleClass> findClasses(String name) {
        if (StringUtils.isBlank(name)) {
            return Collections.emptyList();
        }

        final List<BundleClass> results = Lists.newArrayList();
        for (Bundle bundle : context.getBundles()) {
            final BundleClass clazz = findClass(bundle, name);
            if (clazz != null) {
                results.add(clazz);
            }
        }

        return results;
    }

    public List<BundleClass> findClasses(List<String> bundleIdAndClassNames) {
        return FluentIterable.from(bundleIdAndClassNames).transform(new Function<String, BundleClass>() {
            @Override
            public BundleClass apply(String bundleIdAndClassName) {
                List<String> parts = Splitter.on(",").omitEmptyStrings().trimResults().splitToList(bundleIdAndClassName);

                return findClass(parts.get(0), parts.get(1));
            }
        }).filter(Predicates.<BundleClass>notNull()).toList();
    }

    public Set<BundleClass> findClasses(List<String> bundleIds, List<String> bundleClasses) {
        Set<BundleClass> result = Sets.newLinkedHashSet();

        for (Bundle bundle : findBundles(bundleIds)) {
            result.addAll(findClasses(bundle));
        }

        result.addAll(findClasses(bundleClasses));

        return result;
    }

    public BundleClass findClass(Configuration configuration) {
        BundleClass clazz = null;
        Bundle bundle;

        ServiceReference serviceReference = findServiceReference(configuration.getPid());
        if (serviceReference == null) {
            serviceReference = findServiceReference(configuration.getFactoryPid());
        }

        if (serviceReference != null) {
            bundle = serviceReference.getBundle();

            Object service = referenceService(serviceReference);
            if (service != null) {
                clazz = new BundleClass(bundle, service.getClass().getName());
            }
        } else {
            bundle = findBundle(configuration);
        }

        String classPid = StringUtils.defaultIfBlank(configuration.getFactoryPid(), configuration.getPid());
        if (clazz == null && bundle != null) {
            clazz = findClass(bundle, classPid);
        }

        if (clazz != null) {
            clazz = obtainClassMetadata(clazz, classPid);
        }

        return clazz;
    }

    private BundleClass obtainClassMetadata(BundleClass clazz, String classPid) {
        if (clazz != null) {
            ObjectClassDefinition definition = getClassDefinition(clazz.getBundle(), classPid);
            if (definition != null) {
                clazz = new MetaTypeBundleClass(clazz, definition);
            }
        }

        return clazz;
    }

    public Bundle findBundle(String id) {
        final Long longId = Longs.tryParse(id);

        return longId != null ? context.getBundle(longId) : null;
    }

    public Iterable<Bundle> findBundles(List<String> ids) {
        return FluentIterable.from(ids).transform(new Function<String, Bundle>() {
            @Override
            public Bundle apply(String id) {
                return findBundle(id);
            }
        }).filter(Predicates.<Bundle>notNull());
    }

    public Bundle findBundle(final Configuration configuration) {
        return FluentIterable.from(context.getBundles()).firstMatch(new Predicate<Bundle>() {
            @Override
            public boolean apply(Bundle bundle) {
                return StringUtils.equals(configuration.getBundleLocation(), bundle.getLocation());
            }
        }).orNull();
    }

    public BundleClass findClass(ServiceReference serviceReference) {
        BundleClass clazz = null;

        Bundle bundle = serviceReference.getBundle();
        String pid = (String) serviceReference.getProperty(Constants.SERVICE_PID);
        Object service = referenceService(serviceReference);

        if (service != null) {
            clazz = new BundleClass(bundle, service.getClass().getName());
        } else if (StringUtils.isNotBlank(pid)) {
            clazz = findClass(bundle, pid);
        }

        if (clazz != null) {
            clazz = obtainClassMetadata(clazz, pid);
        }

        return clazz;
    }

    public BundleClass findClass(String bundleId, String name) {
        final Bundle bundle = findBundle(bundleId);

        return bundle != null ? findClass(bundle, name) : null;
    }

    public BundleClass findClass(Bundle bundle, String name) {
        for (final String className : new BundleScanner(bundle).findClassNames(name)) {
            if (StringUtils.equals(className, name)) {
                return new BundleClass(bundle, name);
            }
        }

        return null;
    }

    public ObjectClassDefinition getClassDefinition(Bundle bundle, String pid) {
        ObjectClassDefinition result = null;

        if (bundle != null) {
            try {
                final MetaTypeInformation info = metaTypeService.getMetaTypeInformation(bundle);

                if (info != null) {
                    result = info.getObjectClassDefinition(pid, null);
                }
            } catch (Throwable e) { //NOSONAR
                // ignore
            }
        }

        return result;
    }

    @SuppressWarnings("unchecked")
    public <T> T service(Class<T> clazz) {
        ServiceReference reference = context.getServiceReference(clazz.getName());

        return reference != null ? (T) context.getService(reference) : null;
    }

    public Object findService(final String pid) {
        return referenceService(findServiceReference(pid));
    }

    public Object referenceService(ServiceReference reference) {
        Object service = null;
        if (reference != null) {
            try {
                service = context.getService(reference);
            } catch (NoClassDefFoundError e) { //NOSONAR
                // ignore classloading issues etc
            }
        }

        return service;
    }

    public ServiceReference findServiceReference(final String pid) {
        ServiceReference result = null;

        if (StringUtils.isNotBlank(pid)) {
            try {
                result = FluentIterable.from(context.getAllServiceReferences(null, null))
                        .firstMatch(new Predicate<ServiceReference>() {
                            @Override
                            public boolean apply(ServiceReference serviceReference) {
                                return StringUtils.equals(pid, (String) serviceReference.getProperty(Constants.SERVICE_PID));
                            }
                        }).orNull();
            } catch (InvalidSyntaxException e) { // NOSONAR
                // ignore
            }
        }

        return result;
    }

    public Iterable<ServiceReference> getServiceReferences() {
        try {
            return FluentIterable.from(context.getAllServiceReferences(null, null));
        } catch (InvalidSyntaxException e) {
            LOG.error("Cannot obtain references during service search.", e);
        }

        return Collections.emptyList();
    }

    public Iterable<Configuration> getConfigurations() {
        Iterable<Configuration> configurations = Collections.emptyList();

        if (configAdmin != null) {
            try {
                configurations = FluentIterable.from(configAdmin.listConfigurations(null));
            } catch (IOException | InvalidSyntaxException e) {
                LOG.error("Cannot read configurations", e);
            }
        }

        return configurations;
    }

    public BundleContext getContext() {
        return context;
    }
} public class SearchPaths {

	public static final String APP_NAME = "search";

	private static final String APP_ROOT_PROP = "felix.webconsole.manager.root";

	private static final String APP_ROOT_DEFAULT = "/system/console";

	private final BundleContext context;

	public SearchPaths(BundleContext context) {
		this.context = context;
	}

	public static SearchPaths from(BundleContext context) {
		return new SearchPaths(context);
	}

	public String appRoot() {
		return StringUtils.defaultIfEmpty(context.getProperty(APP_ROOT_PROP), APP_ROOT_DEFAULT);
	}

	public String appAlias(String alias) {
		return appRoot() + "/" + alias;
	}

	public String pluginRoot() {
		return appRoot() + "/" + APP_NAME;
	}

	public String pluginAlias(String alias) {
		return pluginRoot() + "/" + alias;
	}
} public class BundleClass {

    private Bundle bundle;

    private String className;

    public BundleClass(Bundle bundle, String className) {
        this.bundle = bundle;
        this.className = className;
    }

    public Bundle getBundle() {
        return bundle;
    }

    public String getClassName() {
        return className;
    }

    public String getClassPath() {
        return StringUtils.replace(className, ".", "/");
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        BundleClass that = (BundleClass) o;

        return new EqualsBuilder()
                .append(bundle, that.bundle)
                .append(className, that.className)
                .isEquals();
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder()
                .append(bundle)
                .append(className)
                .toHashCode();
    }
} public class MetaTypeBundleClass extends BundleClass {

	private final ObjectClassDefinition definition;

	public MetaTypeBundleClass(Bundle bundle, String className, ObjectClassDefinition definition) {
		super(bundle, className);
		this.definition = definition;
	}

	public MetaTypeBundleClass(BundleClass base, ObjectClassDefinition definition) {
		this(base.getBundle(), base.getClassName(), definition);
	}

	public ObjectClassDefinition getDefinition() {
		return definition;
	}
} public class SearchParams {

	private static final int RESULT_LIMIT_DEFAULT = 60;

	private static final boolean CACHED_DEFAULT = true;

	private final String phrase;

	private final int resultLimit;

	private final List<String> providers;

	private final boolean cached;

	public SearchParams(String phrase, List<String> providers) {
		this(phrase, providers, RESULT_LIMIT_DEFAULT, CACHED_DEFAULT);
	}

	public SearchParams(String phrase, List<String> providers, Integer resultLimit, Boolean cached) {
		this.phrase = phrase;
		this.providers = FluentIterable.from(providers).filter(new Predicate<String>() {
			@Override
			public boolean apply(String s) {
				return StringUtils.isNotBlank(s);
			}
		}).toList();
		this.resultLimit = MoreObjects.firstNonNull(resultLimit, RESULT_LIMIT_DEFAULT);
		this.cached = MoreObjects.firstNonNull(cached, CACHED_DEFAULT);
	}

	public String getPhrase() {
		return phrase;
	}

	public int getResultLimit() {
		return resultLimit;
	}

	public List<String> getProviders() {
		return this.providers;
	}

	public boolean isCached() {
		return cached;
	}

	@Override
	public int hashCode() {
		final HashCodeBuilder builder = new HashCodeBuilder();
		builder.append(phrase);
		builder.append(resultLimit);
		builder.append(providers);

		return builder.toHashCode();
	}
} public class SearchResult implements Serializable {

	// Serializables

	private final String id;

	private final String provider;

	private final String label;

	private String description;

	private Map<String, Object> context;

	// Internally used for post-processing

	private transient int rank;

	private transient List<String> phrases;

	public SearchResult(String provider, String id, int rank, String label) {
		this.provider = provider;
		this.rank = rank;
		this.label = label;
		this.context = Maps.newHashMap();

		this.id = id;
		this.phrases = Lists.newArrayList();
		this.phrases.add(label);
	}

	public SearchResult(String provider, String id, int rank, String label, String description) {
		this(provider, id, rank, label);
		this.description = description;
	}

	public SearchResult(String provider, String id, int rank, String label, String description, Map<String, Object> context) {
		this(provider, id, rank, label, description);
		this.context = context;
	}

	public String getProvider() {
		return provider;
	}

	public String getId() {
		return id;
	}

	public int getRank() {
		return rank;
	}

	public String getLabel() {
		return label;
	}

	public String getDescription() {
		return description;
	}

	public Map<String, Object> getContext() {
		return context;
	}

	/**
	 * Get phrases which should be compared to queries phrase
	 */
	public List<String> getPhrases() {
		return ImmutableList.copyOf(phrases);
	}

	public SearchResult addPhrase(String phrase) {
		if (StringUtils.isNotBlank(phrase)) {
			phrases.add(phrase);
		}

		return this;
	}
} public class SearchUtils {

	private static final String WORD_DELIMITER = " ";

	private static final int PHRASE_MIN_WORD_LENGTH_DEFAULT = 3;

	public static final String LINE_DELIMITER = "\n";

	private SearchUtils() {
		// cannot be constructed
	}

	/**
	 * Determine whether specified result ranking should be boosted
	 */
	public static boolean isResultRankBoosted(SearchResult result, String text) {
		for (String phrase : result.getPhrases()) {
			final String p = StringUtils.trimToEmpty(phrase).toLowerCase();
			final String t = StringUtils.trimToEmpty(text).toLowerCase();

			if (!p.isEmpty() && !t.isEmpty() && (p.startsWith(t) || p.endsWith(t))) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Calculate search result score basing on input phrase (comparator function)
	 */
	public static int compareToPhrase(SearchResult sr1, SearchResult sr2, String text, int rankBoost) {
		final String phrase = StringUtils.trimToEmpty(text.toLowerCase());

		int r1 = sr1.getRank();
		int r2 = sr2.getRank();

		// Rank boost if one of phrases starts or ends with
		if (isResultRankBoosted(sr1, text)) {
			r1 -= rankBoost;
		}
		if (isResultRankBoosted(sr2, text)) {
			r2 -= rankBoost;
		}

		// Differences in rankings
		if (r1 != r2) {
			return r1 - r2;
		}

		// Substrings detection
		final String s1 = sr1.getLabel().toLowerCase();
		final String s2 = sr2.getLabel().toLowerCase();

		if ((s1.contains(text) && s2.contains(text)) || (!s1.contains(text) && !s2.contains(text))) {
			final int d1 = StringUtils.getLevenshteinDistance(phrase, s1);
			final int d2 = StringUtils.getLevenshteinDistance(phrase, s2);

			return d1 - d2;
		}

		if (s1.contains(text)) {
			return -1;
		}

		return 1;
	}

	/**
	 * Check whether phrase contains specified text (with multiple word support)
	 */
	public static boolean containsPhrase(final String phrase, final String text, int minWordLength) {
		if (FilenameUtils.wildcardMatch(text, phrase, IOCase.INSENSITIVE)) {
			return true;
		}

		for (String phraseWord : splitWords(phrase)) {
			for (String textWord : splitWords(text)) {
				final String p = StringUtils.trimToEmpty(phraseWord);
				final String t = StringUtils.trimToEmpty(textWord);

				if ((!p.isEmpty() && !t.isEmpty()) && (t.length() >= minWordLength) && (p.length()
						>= minWordLength) && (p.contains(t) || t.contains(p))) {
					return true;
				}
			}
		}

		return false;
	}

	private static String[] splitWords(String phrase) {
		return StringUtils.trimToEmpty(phrase).toLowerCase().split(WORD_DELIMITER);
	}

	/**
	 * Check whether phrase contains one of specified texts (with multiple word support)
	 */
	public static boolean containsPhrase(final String phrase, List<String> texts, int minWordLength) {
		for (String text : texts) {
			if (containsPhrase(phrase, text, minWordLength)) {
				return true;
			}
		}

		return false;
	}

	public static boolean containsPhrase(final String phrase, List<String> texts) {
		return containsPhrase(phrase, texts, PHRASE_MIN_WORD_LENGTH_DEFAULT);
	}

	public static boolean containsPhrase(final String phrase, String text) {
		return containsPhrase(phrase, Collections.singletonList(text));
	}

	/**
	 * Compose human readable parameter list as description
	 */
	public static String composeDescription(Map<String, Object> params) {
		List<String> lines = Lists.newArrayList();
		for (Map.Entry<String, Object> entry : params.entrySet()) {
			Object value = entry.getValue();
			if (value instanceof String[]) {
				value = "[" + StringUtils.join((String[] )value, ", ") + "]";
			}

			lines.add(String.format("%s: %s", entry.getKey(), value));
		}

		return StringUtils.join(lines, "\n");
	}

	public static List<String> findContexts(String phrase, String source, int contextLineCount) {
		List<String> contexts = Lists.newLinkedList();
		List<String> lines = Splitter.on(LINE_DELIMITER).splitToList(source);

		int i = 0;
		for (String line : lines) {
			if (StringUtils.containsIgnoreCase(line, phrase)) {
				String before = Joiner.on("\n")
						.join(lines.subList(Math.max(0, i - contextLineCount - 1), Math.max(0, i - 1)));
				String after = Joiner.on("\n").join(lines.subList(Math.min(i + 1, lines.size()),
						Math.min(lines.size(), i + 1 + contextLineCount)));
				String context = PrettifierUtils.escape(before) + "\n" + PrettifierUtils
						.highlight(PrettifierUtils.escape(line)) + "\n" + PrettifierUtils.escape(after);

				contexts.add(context);
			}

			i++;
		}

		return contexts;
	}
} public class BundleDownloadServlet extends RestServlet {

    public static final String ALIAS_NAME = "bundle-download";

    public static final String ID_PARAM = "id";

    private final OsgiExplorer osgiExplorer;

    public BundleDownloadServlet(BundleContext bundleContext) {
        super(bundleContext);
        this.osgiExplorer = new OsgiExplorer(bundleContext);
    }

    public static String url(BundleContext context, Bundle bundle) {
        return url(context, bundle.getBundleId());
    }

    public static String url(BundleContext context, long bundleId) {
        return String.format(SearchPaths.from(context).pluginAlias(ALIAS_NAME) + "?" + ID_PARAM + "=%d", bundleId);
    }

    @Override
    protected String getAliasName() {
        return ALIAS_NAME;
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        final String bundleId = StringUtils.trimToEmpty(request.getParameter(ID_PARAM));

        final File jar = osgiExplorer.findJar(Long.valueOf(bundleId));
        if (jar == null) {
            JsonUtils.writeMessage(response, MessageType.ERROR, String.format("Bundle '%s' JAR cannot be found.", bundleId));
            return;
        }

        final String jarName = osgiExplorer.proposeJarName(bundleId);
        final FileDownloader downloader = new FileDownloader(response, new FileInputStream(jar), jarName);

        downloader.download();
    }

} public class ClassDecompileServlet extends RestServlet {

	public static final String ALIAS_NAME = "class-decompile";

	public static final String BUNDLE_ID = "bundleId";

	public static final String CLASS_NAME = "className";

	private final OsgiExplorer osgiExplorer;

	public ClassDecompileServlet(BundleContext bundleContext) {
		super(bundleContext);
		this.osgiExplorer = new OsgiExplorer(bundleContext);
	}

	public static String url(BundleContext context, Bundle bundle, String className) {
		return url(context, bundle.getBundleId(), className);
	}

	public static String url(BundleContext context, long bundleId, String className) {
		return String.format("%s?%s=%d&%s=%s", SearchPaths.from(context).pluginAlias(ALIAS_NAME), BUNDLE_ID, bundleId, CLASS_NAME, className);
	}

	@Override
	protected String getAliasName() {
		return ALIAS_NAME;
	}

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		final String bundleId = StringUtils.trimToEmpty(request.getParameter(BUNDLE_ID));
		final String className = StringUtils.trimToEmpty(request.getParameter(CLASS_NAME));

		final Bundle bundle = bundleContext.getBundle(Long.valueOf(bundleId));
		if (bundle == null) {
			JsonUtils.writeMessage(response, MessageType.ERROR, String.format("Bundle '%s' not be found.", bundleId));
			return;
		}

		final File bundleJar = osgiExplorer.findJar(Long.valueOf(bundleId));
		if (bundleJar == null) {
			JsonUtils.writeMessage(response, MessageType.ERROR, String.format("Bundle '%s' JAR cannot be found.", bundleId));
			return;
		}

		final String classSource = osgiExplorer.decompileClass(bundleJar, className);

		JsonUtils.writeMessage(response, MessageType.SUCCESS, "Class details",
				ImmutableMap.of(
						"className", className,
						"classSource", classSource,
						"bundleId", bundleId,
						"bundleSymbolicName", bundle.getSymbolicName(),
						"bundleJarPath", bundleJar.getAbsolutePath()
				));
	}

} public class BundleUtils {

	public static String consolePath(BundleContext context, Bundle bundle) {
		return consolePath(context, bundle.getBundleId());
	}

	public static String consolePath(BundleContext context, long bundleId) {
		return SearchPaths.from(context).appAlias("bundles") + "/" + String.valueOf(bundleId);
	}

	public static String mapState(int state) {
		switch (state) {
			case Bundle.ACTIVE:
				return "active";
			case Bundle.INSTALLED:
				return "installed";
			case Bundle.RESOLVED:
				return "resolved";
			case Bundle.UNINSTALLED:
				return "uninstalled";
			default:
				return "unknown";
		}
	}

	public static Map<String, String> context(Bundle bundle) {
		return ImmutableMap.of(
				"bundleId", String.valueOf(bundle.getBundleId()),
				"bundleDescription", description(bundle)
		);
	}

	public static String description(Bundle bundle) {
		return StringUtils.defaultString((String) bundle.getHeaders().get(Constants.BUNDLE_NAME), bundle.getSymbolicName());
	}

	public static String descriptionWithId(Bundle bundle) {
		return String.format("%s (%d)", description(bundle), bundle.getBundleId());
	}


}
import com.neva.felix.webconsole.plugins.search.core.OsgiExplorer;
import com.neva.felix.webconsole.plugins.search.core.SearchPaths;
import com.neva.felix.webconsole.plugins.search.core.BundleClass;
import com.neva.felix.webconsole.plugins.search.core.MetaTypeBundleClass;
import com.neva.felix.webconsole.plugins.search.core.SearchParams;
import com.neva.felix.webconsole.plugins.search.core.SearchResult;
import com.neva.felix.webconsole.plugins.search.core.SearchUtils;
import com.neva.felix.webconsole.plugins.search.rest.BundleDownloadServlet;
import com.neva.felix.webconsole.plugins.search.rest.ClassDecompileServlet;
import com.neva.felix.webconsole.plugins.search.utils.BundleUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.metatype.ObjectClassDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
package com.neva.felix.webconsole.plugins.search.core.provider;



public class ServiceSearchProvider extends AbstractSearchProvider {

	public static final String LABEL = "Service";

	private static final Logger LOG = LoggerFactory.getLogger(ClassSearchProvider.class);

	private static final int RESULT_RANK = 400;

	private final OsgiExplorer osgiExplorer;

	public ServiceSearchProvider(BundleContext bundleContext) {
		super(bundleContext);
		this.osgiExplorer = new OsgiExplorer(bundleContext);
	}

	@Override
	@SuppressWarnings("unchecked")
	public List<SearchResult> search(SearchParams params) {