blob: a2c64541ce422c63ceb4696159b6fb3c746cd584 [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.sling.tooling.support.source.impl;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import javax.servlet.Servlet;
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.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.apache.felix.utils.json.JSONWriter;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The <tt>SourceReferencesServlet</tt> infers and outputs source information about bundles running a Sling instance
*/
@Component
@Service(value = Servlet.class)
@Property(name="alias", value="/system/sling/tooling/sourceReferences.json")
public class SourceReferencesServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private final Logger log = LoggerFactory.getLogger(getClass());
private static final String KEY_TYPE = "__type__";
private static final String KEY_GROUP_ID = "groupId";
private static final String KEY_ARTIFACT_ID = "artifactId";
private static final String KEY_VERSION = "version";
static final String VALUE_TYPE_MAVEN = "maven";
private static final String FELIX_FW_GROUP_ID = "org.apache.felix";
private static final String FELIX_FW_ARTIFACT_ID = "org.apache.felix.framework";
private ComponentContext ctx;
private List<SourceReferenceFinder> finders;
protected void activate(ComponentContext ctx) {
this.ctx = ctx;
finders = new ArrayList<SourceReferenceFinder>();
finders.add(new FelixJettySourceReferenceFinder());
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
response.setContentType("application/json");
final JSONWriter w = new JSONWriter(response.getWriter());
w.array();
for ( Bundle bundle : ctx.getBundleContext().getBundles() ) {
// skip bundle if it is a fragment (http://stackoverflow.com/questions/11655295/using-the-osgi-api-how-do-i-find-out-if-a-given-bundle-is-a-fragment)
if ((bundle.adapt(BundleRevision.class).getTypes() & BundleRevision.TYPE_FRAGMENT) != 0) {
log.debug("Skip bundle '{}' because it is a fragment", bundle);
// source references should only be listed with the host bundle
continue;
}
Object bundleVersion = bundle.getHeaders().get(Constants.BUNDLE_VERSION);
w.object();
w.key(Constants.BUNDLE_SYMBOLICNAME);
w.value(bundle.getSymbolicName());
w.key(Constants.BUNDLE_VERSION);
w.value(bundleVersion);
w.key("sourceReferences");
w.array();
// the system bundle is embedded by the launchpad jar so we need special handling
// since the pom.properties file is not located in the bundle
if ( bundle.getBundleId() == 0 && bundle.getSymbolicName().equals(FELIX_FW_ARTIFACT_ID) ) {
writeMavenGav(w, FELIX_FW_GROUP_ID, FELIX_FW_ARTIFACT_ID, (String) bundleVersion);
}
// look for pom.properties in the bundle ( the original bundle, fragments )
collectMavenSourceReferences(w, bundle);
// look for pom.properties in jars embedded in the bundle
for ( String jar : getEmbeddedJars(bundle)) {
URL entry = bundle.getEntry(jar);
// incorrect or inaccessible entry
if ( entry == null ) {
continue;
}
collectMavenSourceRerefences(w, entry);
}
// query custom finders for source references
for ( SourceReferenceFinder finder : finders ) {
try {
for ( SourceReference reference : finder.findSourceReferences(bundle)) {
log.debug("{} found reference {}:{}:{} in {}", new Object[] { finder, reference.getGroupId(), reference.getArtifactId(), reference.getVersion(), bundle});
writeMavenGav(w, reference.getGroupId(), reference.getArtifactId(), reference.getVersion());
}
} catch (SourceReferenceException e) {
log.warn(finder + " execution did not complete normally for " + bundle, e);
}
}
w.endArray();
w.endObject();
}
w.endArray();
} catch (IOException e) {
throw new ServletException(e);
}
}
private void collectMavenSourceReferences(JSONWriter w, Bundle bundle) throws IOException {
Enumeration<?> entries = bundle.findEntries("/META-INF/maven", "pom.properties", true);
while ( entries != null && entries.hasMoreElements()) {
URL entry = (URL) entries.nextElement();
InputStream in = entry.openStream();
try {
writeMavenGav(w, in);
} finally {
IOUtils.closeQuietly(in);
}
}
}
private void writeMavenGav(JSONWriter w, String groupId, String artifactId, String version) throws IOException {
w.object();
w.key(KEY_TYPE).value(VALUE_TYPE_MAVEN);
w.key(KEY_GROUP_ID).value(groupId);
w.key(KEY_ARTIFACT_ID).value(artifactId);
w.key(KEY_VERSION).value(version);
w.endObject();
}
private void writeMavenGav(JSONWriter w, InputStream in) throws IOException {
Properties p = new Properties();
p.load(in);
w.object();
w.key(KEY_TYPE).value(VALUE_TYPE_MAVEN);
for ( String prop : new String[] { KEY_GROUP_ID, KEY_ARTIFACT_ID, KEY_VERSION} ) {
w.key(prop).value(p.getProperty(prop));
}
w.endObject();
}
private List<String> getEmbeddedJars(Bundle bundle) {
String classPath = (String) bundle.getHeaders().get(Constants.BUNDLE_CLASSPATH);
if ( classPath == null ) {
return Collections.emptyList();
}
List<String> embeddedJars = new ArrayList<String>();
String[] classPathEntryNames = classPath.split("\\,");
for ( String classPathEntry : classPathEntryNames ) {
if ( classPathEntry.endsWith(".jar")) {
embeddedJars.add(classPathEntry);
}
}
return embeddedJars;
}
private void collectMavenSourceRerefences(JSONWriter w, URL entry) throws IOException {
InputStream wrappedIn = entry.openStream();
try {
JarInputStream jarIs = new JarInputStream(wrappedIn);
JarEntry jarEntry;
while ( ( jarEntry = jarIs.getNextJarEntry()) != null ) {
String entryName = jarEntry.getName();
if ( entryName.startsWith("META-INF/maven/") && entryName.endsWith("/pom.properties")) {
writeMavenGav(w, jarIs);
}
}
} finally {
IOUtils.closeQuietly(wrappedIn);
}
}
}