blob: 23672e1c41baad7fc5ea573e8f26ce0f0a15176a [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.adapter.internal;
import static org.apache.sling.api.adapter.AdapterFactory.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
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.commons.lang.builder.CompareToBuilder;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.commons.json.JSONArray;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.JSONObject;
import org.apache.sling.commons.osgi.OsgiUtil;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentContext;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component
@Service(Servlet.class)
@Properties({ @Property(name = Constants.SERVICE_DESCRIPTION, value = "Adapter Web Console Plugin"),
@Property(name = Constants.SERVICE_VENDOR, value = "The Apache Software Foundation"),
@Property(name = "felix.webconsole.label", value = "adapters"),
@Property(name = "felix.webconsole.title", value = "Sling Adapters"),
@Property(name = "felix.webconsole.css", value = "/adapters/res/ui/adapters.css"),
@Property(name = "felix.webconsole.configprinter.modes", value = "always") })
@SuppressWarnings("serial")
public class AdapterWebConsolePlugin extends HttpServlet implements ServiceTrackerCustomizer, BundleListener {
private static final int INDENT = 4;
private static final String ADAPTER_CONDITION = "adapter.condition";
private Logger logger = LoggerFactory.getLogger(AdapterWebConsolePlugin.class);
private List<AdaptableDescription> allAdaptables;
private Map<Object, List<AdaptableDescription>> adapterServices;
private Map<Bundle, List<AdaptableDescription>> adapterBundles;
private ServiceTracker adapterTracker;
private BundleContext bundleContext;
public Object addingService(ServiceReference reference) {
Object service = this.bundleContext.getService(reference);
addServiceMetadata(reference, service);
return service;
}
private void addServiceMetadata(ServiceReference reference, Object service) {
final String[] adapters = OsgiUtil.toStringArray(reference.getProperty(ADAPTER_CLASSES));
final String condition = OsgiUtil.toString(reference.getProperty(ADAPTER_CONDITION), null);
final String[] adaptables = OsgiUtil.toStringArray(reference.getProperty(ADAPTABLE_CLASSES));
final List<AdaptableDescription> descriptions = new ArrayList<AdaptableDescription>(adaptables.length);
for (final String adaptable : adaptables) {
descriptions.add(new AdaptableDescription(reference.getBundle(), adaptable, adapters, condition));
}
adapterServices.put(service, descriptions);
update();
}
public void bundleChanged(BundleEvent event) {
if (event.getType() == BundleEvent.STOPPED) {
removeBundle(event.getBundle());
} else if (event.getType() == BundleEvent.STARTED) {
addBundle(event.getBundle());
}
}
public void modifiedService(ServiceReference reference, Object service) {
addServiceMetadata(reference, service);
}
public void removedService(ServiceReference reference, Object service) {
adapterServices.remove(service);
}
@SuppressWarnings("unchecked")
private void addBundle(Bundle bundle) {
List<AdaptableDescription> descs = new ArrayList<AdaptableDescription>();
try {
Enumeration<URL> files = bundle.getResources("SLING-INF/adapters.json");
if (files != null) {
while (files.hasMoreElements()) {
InputStream stream = files.nextElement().openStream();
String contents = IOUtils.toString(stream);
IOUtils.closeQuietly(stream);
JSONObject obj = new JSONObject(contents);
for (Iterator<String> adaptableNames = obj.keys(); adaptableNames.hasNext();) {
String adaptableName = adaptableNames.next();
JSONObject adaptable = obj.getJSONObject(adaptableName);
for (Iterator<String> conditions = adaptable.keys(); conditions.hasNext();) {
String condition = conditions.next();
String[] adapters;
Object value = adaptable.get(condition);
if (value instanceof JSONArray) {
adapters = toStringArray((JSONArray) value);
} else {
adapters = new String[] { value.toString() };
}
descs.add(new AdaptableDescription(bundle, adaptableName, adapters, condition));
}
}
}
}
if (!descs.isEmpty()) {
adapterBundles.put(bundle, descs);
update();
}
} catch (IOException e) {
logger.error("Unable to load adapter descriptors for bundle " + bundle, e);
} catch (JSONException e) {
logger.error("Unable to load adapter descriptors for bundle " + bundle, e);
}
}
private String[] toStringArray(JSONArray value) throws JSONException {
List<String> result = new ArrayList<String>(value.length());
for (int i = 0; i < value.length(); i++) {
result.add(value.getString(i));
}
return result.toArray(new String[value.length()]);
}
private void removeBundle(Bundle bundle) {
adapterBundles.remove(bundle);
update();
}
private void update() {
final List<AdaptableDescription> newList = new ArrayList<AdaptableDescription>();
for (final List<AdaptableDescription> descriptions : adapterServices.values()) {
newList.addAll(descriptions);
}
for (final List<AdaptableDescription> list : adapterBundles.values()) {
newList.addAll(list);
}
Collections.sort(newList);
allAdaptables = newList;
}
protected void activate(ComponentContext ctx) throws InvalidSyntaxException {
this.bundleContext = ctx.getBundleContext();
this.adapterServices = new HashMap<Object, List<AdaptableDescription>>();
this.adapterBundles = new HashMap<Bundle, List<AdaptableDescription>>();
for (Bundle bundle : this.bundleContext.getBundles()) {
if (bundle.getState() == Bundle.ACTIVE) {
addBundle(bundle);
}
}
this.bundleContext.addBundleListener(this);
Filter filter = this.bundleContext.createFilter("(&(adaptables=*)(adapters=*))");
this.adapterTracker = new ServiceTracker(this.bundleContext, filter, this);
this.adapterTracker.open();
}
protected void deactivate(ComponentContext ctx) {
this.adapterTracker.close();
this.adapterServices = null;
this.adapterBundles = null;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if (req.getPathInfo().endsWith("/data.json")) {
getJson(resp);
} else {
getHtml(resp);
}
}
private void getJson(final HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json");
try {
final JSONObject obj = new JSONObject();
for (final AdaptableDescription desc : allAdaptables) {
final JSONObject adaptableObj;
if (obj.has(desc.adaptable)) {
adaptableObj = obj.getJSONObject(desc.adaptable);
} else {
adaptableObj = new JSONObject();
obj.put(desc.adaptable, adaptableObj);
}
for (final String adapter : desc.adapters) {
adaptableObj.accumulate(desc.condition == null ? "" : desc.condition, adapter);
}
}
resp.getWriter().println(obj.toString(INDENT));
} catch (JSONException e) {
throw new ServletException("Unable to produce JSON", e);
}
}
private void getHtml(HttpServletResponse resp) throws IOException {
final PrintWriter writer = resp.getWriter();
writer.println("<p class=\"statline ui-state-highlight\">${Introduction}</p>");
writer.println("<p>${intro}</p>");
writer.println("<p class=\"statline ui-state-highlight\">${How to Use This Information}</p>");
writer.println("<p>${usage}</p>");
writer.println("<table class=\"adapters nicetable\">");
writer.println("<thead><tr><th class=\"header\">${Adaptable Class}</th><th class=\"header\">${Adapter Class}</th><th class=\"header\">${Condition}</th><th class=\"header\">${Providing Bundle}</th></tr></thead>");
String rowClass = "odd";
for (final AdaptableDescription desc : allAdaptables) {
writer.printf("<tr class=\"%s ui-state-default\"><td>%s</td>", rowClass, desc.adaptable);
writer.print("<td>");
for (final String adapter : desc.adapters) {
writer.print(adapter);
writer.print("<br/>");
}
writer.print("</td>");
if (desc.condition == null) {
writer.print("<td>&nbsp;</td>");
} else {
writer.printf("<td>%s</td>", desc.condition);
}
writer.printf("<td><a href=\"${pluginRoot}/../bundles/%s\">%s (%s)</a></td>", desc.bundle.getBundleId(),
desc.bundle.getSymbolicName(), desc.bundle.getBundleId());
writer.println("</tr>");
if (rowClass.equals("odd")) {
rowClass = "even";
} else {
rowClass = "odd";
}
}
writer.println("</table>");
}
public void printConfiguration(final PrintWriter pw) {
pw.println("Current Apache Sling Adaptables:");
for (final AdaptableDescription desc : allAdaptables) {
pw.printf("Adaptable: %s\n", desc.adaptable);
if (desc.condition != null) {
pw.printf("Condition: %s\n", desc.condition);
}
pw.printf("Providing Bundle: %s\n", desc.bundle.getSymbolicName());
pw.printf("Available Adapters:\n");
for (final String adapter : desc.adapters) {
pw.print(" * ");
pw.println(adapter);
}
pw.println();
}
}
/**
* Method to retreive static resources from this bundle.
*/
@SuppressWarnings("unused")
private URL getResource(String path) {
if (path.startsWith("/adapters/res/ui/")) {
return this.getClass().getResource(path.substring(9));
}
return null;
}
class AdaptableDescription implements Comparable<AdaptableDescription> {
private final String adaptable;
private final String[] adapters;
private final String condition;
private final Bundle bundle;
public AdaptableDescription(final Bundle bundle, final String adaptable, final String[] adapters,
final String condition) {
this.adaptable = adaptable;
this.adapters = adapters;
this.condition = condition;
this.bundle = bundle;
}
@Override
public String toString() {
return "AdapterDescription [adaptable=" + this.adaptable + ", adapters=" + Arrays.toString(this.adapters)
+ ", condition=" + this.condition + ", bundle=" + this.bundle + "]";
}
public int compareTo(AdaptableDescription o) {
return new CompareToBuilder().append(this.adaptable, o.adaptable).append(this.condition, o.condition)
.append(this.adapters.length, o.adapters.length)
.append(this.bundle.getBundleId(), o.bundle.getBundleId()).toComparison();
}
}
}