/* | |
* 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.webconsole.plugins.packageadmin.internal; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.net.URL; | |
import java.util.ArrayList; | |
import java.util.Collection; | |
import java.util.Comparator; | |
import java.util.Enumeration; | |
import java.util.HashMap; | |
import java.util.Iterator; | |
import java.util.LinkedHashSet; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Map.Entry; | |
import java.util.Set; | |
import java.util.SortedSet; | |
import java.util.StringTokenizer; | |
import java.util.TreeMap; | |
import java.util.TreeSet; | |
import javax.servlet.ServletException; | |
import javax.servlet.http.HttpServlet; | |
import javax.servlet.http.HttpServletRequest; | |
import javax.servlet.http.HttpServletResponse; | |
import org.apache.commons.io.IOUtils; | |
import org.apache.felix.utils.json.JSONWriter; | |
import org.apache.felix.utils.manifest.Clause; | |
import org.apache.felix.utils.manifest.Parser; | |
import org.apache.felix.webconsole.SimpleWebConsolePlugin; | |
import org.apache.felix.webconsole.WebConsoleUtil; | |
import org.osgi.framework.Bundle; | |
import org.osgi.framework.BundleContext; | |
import org.osgi.framework.Constants; | |
import org.osgi.service.packageadmin.ExportedPackage; | |
import org.osgi.service.packageadmin.PackageAdmin; | |
/** | |
* Provides a Web bases interface to the Packages Admin service, allowing | |
* the user to find package / maven information and identify duplicate exports. | |
*/ | |
class WebConsolePlugin extends SimpleWebConsolePlugin | |
{ | |
private static final long serialVersionUID = 1L; | |
private static final String LABEL = "depfinder"; //$NON-NLS-1$ | |
private static final String TITLE = "%pluginTitle"; //$NON-NLS-1$ | |
private static final String CATEGORY = "OSGi"; //$NON-NLS-1$ | |
private static final String CSS[] = { "/" + LABEL + "/res/plugin.css" }; //$NON-NLS-1$ //$NON-NLS-2$ | |
@SuppressWarnings("deprecation") | |
private static final Comparator<ExportedPackage> EXPORT_PACKAGE_COMPARATOR = new ExportedPackageComparator(); | |
@SuppressWarnings("deprecation") | |
private final PackageAdmin pa; | |
private final BundleContext bc; | |
// templates | |
private final String TEMPLATE; | |
@SuppressWarnings("deprecation") | |
WebConsolePlugin(BundleContext bc, Object pa) | |
{ | |
super(LABEL, TITLE, CATEGORY, CSS); | |
this.pa = (PackageAdmin) pa; | |
this.bc = bc; | |
// load templates | |
TEMPLATE = readTemplateFile("/res/plugin.html"); //$NON-NLS-1$ | |
} | |
@Override | |
public String getCategory() | |
{ | |
return CATEGORY; | |
} | |
/** | |
* @see org.apache.felix.webconsole.AbstractWebConsolePlugin#renderContent(HttpServletRequest, HttpServletResponse) | |
*/ | |
@Override | |
protected final void renderContent(HttpServletRequest req, | |
HttpServletResponse response) throws ServletException, IOException | |
{ | |
response.getWriter().print(TEMPLATE); | |
} | |
/** | |
* @see HttpServlet#doPost(HttpServletRequest, HttpServletResponse) | |
*/ | |
@Override | |
protected final void doPost(HttpServletRequest req, HttpServletResponse resp) | |
throws ServletException, IOException | |
{ | |
final Object json; | |
String action = req.getParameter("action"); //$NON-NLS-1$ | |
if ("deps".equals(action)) { //$NON-NLS-1$ | |
json = doFindDependencies(req, pa); | |
} | |
else if ("dups".equals(action)) { //$NON-NLS-1$ | |
@SuppressWarnings("deprecation") | |
Map<String, Set<ExportedPackage>> packages = collectExportedPackages( | |
pa, bc); | |
json = doFindDuplicates(packages); | |
} | |
else | |
{ | |
throw new ServletException("Invalid action: " + action); | |
} | |
WebConsoleUtil.setNoCache(resp); | |
resp.setContentType("application/json; utf-8"); //$NON-NLS-1$ | |
JSONWriter writer = new JSONWriter(resp.getWriter()); | |
writer.value(json); | |
writer.flush(); | |
} | |
@SuppressWarnings("deprecation") | |
static final Map<String, Set<ExportedPackage>> collectExportedPackages( | |
final PackageAdmin pa, final BundleContext bundleContext) | |
{ | |
Map<String, Set<ExportedPackage>> exports = new TreeMap<String, Set<ExportedPackage>>(); | |
Bundle[] bundles = bundleContext.getBundles(); | |
for (int i = 0; bundles != null && i < bundles.length; i++) | |
{ | |
final Bundle bundle = bundles[i]; | |
final ExportedPackage[] bundleExports = pa.getExportedPackages(bundle); | |
for (int j = 0; bundleExports != null && j < bundleExports.length; j++) | |
{ | |
final ExportedPackage exportedPackage = bundleExports[j]; | |
Set<ExportedPackage> exportSet = exports.get(exportedPackage.getName()); | |
if (exportSet == null) | |
{ | |
exportSet = new TreeSet<ExportedPackage>( | |
EXPORT_PACKAGE_COMPARATOR); | |
exports.put(exportedPackage.getName(), exportSet); | |
} | |
exportSet.add(exportedPackage); | |
} | |
} | |
return exports; | |
} | |
@SuppressWarnings("deprecation") | |
private static final Map<String, Object> doFindDependencies(HttpServletRequest req, | |
PackageAdmin pa) | |
{ | |
final Map<String, Object> json = new HashMap<String, Object>(); | |
final String findField = req.getParameter("plugin.find"); //$NON-NLS-1$ | |
if (findField != null) | |
{ | |
Set<String> packageNames = getPackageNames(findField); | |
Set<Bundle> exportingBundles = new LinkedHashSet<Bundle>(); | |
for (Iterator<String> i = packageNames.iterator(); i.hasNext();) | |
{ | |
String name = i.next(); | |
@SuppressWarnings("unchecked") | |
List<Object> pl = (List<Object>)json.get("packages"); //$NON-NLS-1$ | |
if ( pl == null ) | |
{ | |
pl = new ArrayList<Object>(); | |
json.put("packages", pl); | |
} | |
pl.add( getPackageInfo(name, pa, exportingBundles)); | |
} | |
final Map<String, Object> mavenJson = new HashMap<String, Object>(); | |
json.put("maven", mavenJson); //$NON-NLS-1$ | |
for (Iterator<Bundle> i = exportingBundles.iterator(); i.hasNext();) | |
{ | |
Bundle bundle = i.next(); | |
final Object value = getMavenInfo(bundle); | |
if ( value != null ) | |
{ | |
mavenJson.put(String.valueOf(bundle.getBundleId()), value); | |
} | |
} | |
} | |
return json; | |
} | |
@SuppressWarnings("deprecation") | |
private static final Collection<Object> doFindDuplicates( | |
final Map<String, Set<ExportedPackage>> exports) | |
{ | |
final List<Object> ret = new ArrayList<Object>(); | |
for (Iterator<Entry<String, Set<ExportedPackage>>> entryIter = exports.entrySet().iterator(); entryIter.hasNext();) | |
{ | |
Entry<String, Set<ExportedPackage>> exportEntry = entryIter.next(); | |
Set<ExportedPackage> exportSet = exportEntry.getValue(); | |
if (exportSet.size() > 1) | |
{ | |
final Map<String, Object> container = new HashMap<String, Object>(); | |
ret.add(container); | |
for (Iterator<ExportedPackage> packageIter = exportSet.iterator(); packageIter.hasNext();) | |
{ | |
ExportedPackage exportedPackage = packageIter.next(); | |
final Map<String, Object> json = toJSON(exportedPackage); | |
container.put("name", exportedPackage.getName()); //$NON-NLS-1$ | |
@SuppressWarnings("unchecked") | |
List<Object> imps = (List<Object>) container.get("entries"); //$NON-NLS-1$ | |
if ( imps == null ) | |
{ | |
imps = new ArrayList<Object>(); | |
container.put("entries", imps); //$NON-NLS-1$ | |
} | |
imps.add(json); | |
} | |
} | |
} | |
return ret; | |
} | |
private static final void toJSON(Bundle bundle, Map<String, Object> json) | |
{ | |
json.put("bid", bundle.getBundleId()); //$NON-NLS-1$ | |
if ( bundle.getSymbolicName() != null ) | |
{ | |
json.put("bsn", bundle.getSymbolicName()); //$NON-NLS-1$ | |
} | |
} | |
private static final void toJSON(final String pkgName, final Bundle[] importers, final Map<String, Object> json) | |
{ | |
for (int i = 0; i < importers.length; i++) | |
{ | |
Bundle bundle = importers[i]; | |
final Map<String, Object> usingJson = new HashMap<String, Object>(); | |
toJSON(bundle, usingJson); | |
final String ip = bundle.getHeaders().get(Constants.IMPORT_PACKAGE); | |
final Clause[] clauses = Parser.parseHeader(ip); | |
for (int j = 0; j < clauses.length; j++) | |
{ | |
Clause clause = clauses[j]; | |
if (pkgName.equals(clause.getName())) | |
{ | |
usingJson.put("ver", clause.getAttribute(Constants.VERSION_ATTRIBUTE)); | |
break; | |
} | |
} | |
@SuppressWarnings("unchecked") | |
List<Object> imps = (List<Object>) json.get("importers"); //$NON-NLS-1$ | |
if ( imps == null ) | |
{ | |
imps = new ArrayList<Object>(); | |
json.put("importers", imps); //$NON-NLS-1$ | |
} | |
imps.add(usingJson); | |
} | |
} | |
@SuppressWarnings("deprecation") | |
private static final Map<String, Object> toJSON(final ExportedPackage pkg) | |
{ | |
final Map<String, Object> ret = new HashMap<String, Object>(); | |
ret.put("version", pkg.getVersion()); //$NON-NLS-1$ | |
toJSON(pkg.getExportingBundle(), ret); | |
toJSON(pkg.getName(), pkg.getImportingBundles(), ret); | |
return ret; | |
} | |
@SuppressWarnings("deprecation") | |
private static final Map<String, Object> getPackageInfo(String packageName, PackageAdmin pa, | |
Set<Bundle> exportingBundles) | |
{ | |
final Map<String, Object> ret = new HashMap<String, Object>(); | |
final ExportedPackage[] exports = pa.getExportedPackages(packageName); | |
for (int i = 0; exports != null && i < exports.length; i++) | |
{ | |
final ExportedPackage x = exports[i]; | |
@SuppressWarnings("unchecked") | |
List<Object> el = (List<Object>)ret.get("exporters"); //$NON-NLS-1$ | |
if ( el == null ) | |
{ | |
el = new ArrayList<Object>(); | |
ret.put("exporters", el); //$NON-NLS-1$ | |
} | |
el.add(toJSON(x)); | |
exportingBundles.add(x.getExportingBundle()); | |
} | |
ret.put("name", packageName); //$NON-NLS-1$ | |
return ret; | |
} | |
private static final Map<Object, Object> getMavenInfo(Bundle bundle) | |
{ | |
Map<Object, Object> ret = null; | |
Enumeration<URL> entries = bundle.findEntries("META-INF/maven", "pom.properties", true); //$NON-NLS-1$ //$NON-NLS-2$ | |
if (entries != null) | |
{ | |
URL u = entries.nextElement(); | |
java.util.Properties props = new java.util.Properties(); | |
InputStream is = null; | |
try | |
{ | |
is = u.openStream(); | |
props.load(u.openStream()); | |
ret = new HashMap<Object, Object>(props); | |
} | |
catch (IOException e) | |
{ | |
// ignore | |
} | |
finally | |
{ | |
IOUtils.closeQuietly(is); | |
} | |
} | |
return ret; | |
} | |
static final Set<String> getPackageNames(String findField) | |
{ | |
StringTokenizer tok = new StringTokenizer(findField, " \t\n\f\r"); //$NON-NLS-1$ | |
SortedSet<String> result = new TreeSet<String>(); | |
while (tok.hasMoreTokens()) | |
{ | |
String part = tok.nextToken().trim(); | |
if (part.length() > 0) | |
{ | |
int idx = part.lastIndexOf('.'); | |
if (idx == part.length() - 1) | |
{ | |
part = part.substring(0, part.length() - 1); | |
idx = part.lastIndexOf('.'); | |
} | |
if (idx != -1) | |
{ | |
char firstCharAfterLastDot = part.charAt(idx + 1); | |
if (Character.isUpperCase(firstCharAfterLastDot)) | |
{ | |
result.add(part.substring(0, idx)); | |
} | |
else | |
{ | |
result.add(part); | |
} | |
} | |
else | |
{ | |
result.add(part); | |
} | |
} | |
} | |
return result; | |
} | |
} |