blob: 99d727635ac9abd436a9491fdfb9af3772ac1c5c [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.resourceresolver.impl.console;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Collection;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import org.apache.sling.api.request.ResponseUtil;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.mapping.ResourceMapper;
import org.apache.sling.api.resource.runtime.RuntimeService;
import org.apache.sling.api.resource.runtime.dto.ResourceProviderDTO;
import org.apache.sling.api.resource.runtime.dto.ResourceProviderFailureDTO;
import org.apache.sling.api.resource.runtime.dto.RuntimeDTO;
import org.apache.sling.resourceresolver.impl.CommonResourceResolverFactoryImpl;
import org.apache.sling.resourceresolver.impl.helper.URI;
import org.apache.sling.resourceresolver.impl.helper.URIException;
import org.apache.sling.resourceresolver.impl.mapping.MapEntriesHandler;
import org.apache.sling.resourceresolver.impl.mapping.MapEntry;
import org.apache.sling.spi.resource.provider.ResourceProvider;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
public class ResourceResolverWebConsolePlugin extends HttpServlet {
private static final long serialVersionUID = 0;
private static final String ATTR_TEST = "plugin.test";
private static final String ATTR_SUBMIT = "plugin.submit";
private static final String PAR_MSG = "msg";
private static final String PAR_TEST = "test";
private final transient CommonResourceResolverFactoryImpl resolverFactory;
private transient ServiceRegistration<Servlet> service;
private final transient RuntimeService runtimeService;
private final transient BundleContext bundleContext;
public ResourceResolverWebConsolePlugin(final BundleContext context,
final CommonResourceResolverFactoryImpl resolverFactory,
final RuntimeService runtimeService) {
this.resolverFactory = resolverFactory;
this.runtimeService = runtimeService;
this.bundleContext = context;
Dictionary<String, Object> props = new Hashtable<String, Object>();
props.put(Constants.SERVICE_DESCRIPTION,
"Apache Sling Resource Resolver Web Console Plugin");
props.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
props.put(Constants.SERVICE_PID, getClass().getName());
props.put("felix.webconsole.label", "jcrresolver");
props.put("felix.webconsole.title", "Resource Resolver");
props.put("felix.webconsole.css", "/jcrresolver/res/ui/resourceresolver.css");
props.put("felix.webconsole.category", "Sling");
props.put("felix.webconsole.configprinter.modes", "always");
service = context.registerService(Servlet.class, this, props);
}
public void dispose() {
if (service != null) {
service.unregister();
service = null;
}
}
@Override
protected void doGet(final HttpServletRequest request,
final HttpServletResponse response) throws ServletException,
IOException {
final String msg = request.getParameter(PAR_MSG);
final String test;
if (msg != null) {
test = request.getParameter(PAR_TEST);
} else {
test = null;
}
final PrintWriter pw = response.getWriter();
pw.println("<table class='content' cellpadding='0' cellspacing='0' width='100%'>");
final MapEntriesHandler mapEntries = resolverFactory.getMapEntries();
titleHtml(pw, "Configuration", null);
pw.println("<tr class='content'>");
pw.println("<td class='content'>Resource Search Path</td>");
pw.print("<td class='content' colspan='2'>");
pw.print(Arrays.asList(resolverFactory.getSearchPath()).toString());
pw.print("</td>");
pw.println("</tr>");
pw.println("<tr class='content'>");
pw.println("<td class='content'>Namespace Mangling</td>");
pw.print("<td class='content' colspan='2'>");
pw.print(resolverFactory.isMangleNamespacePrefixes() ? "Enabled"
: "Disabled");
pw.print("</td>");
pw.println("</tr>");
pw.println("<tr class='content'>");
pw.println("<td class='content'>Mapping Location</td>");
pw.print("<td class='content' colspan='2'>");
pw.print(resolverFactory.getMapRoot());
pw.print("</td>");
pw.println("</tr>");
separatorHtml(pw);
titleHtml(
pw,
"Configuration Test",
"To test the configuration, enter an URL or a resource path into "
+ "the field and click 'Resolve' to resolve the URL or click 'Map' "
+ "to map the resource path. To simulate a map call that takes the "
+ "current request into account, provide a full URL whose "
+ "scheme/host/port prefix will then be used as the request "
+ "information. The path passed to map will always be the path part "
+ "of the URL. In case multiple mapping candidates are found, the "
+ "primary one, which would be returned by ResourceResolver.map, is "
+ "clearly marked, and the others listed for completeness.");
pw.println("<tr class='content'>");
pw.println("<td class='content'>Test</td>");
pw.print("<td class='content' colspan='2'>");
pw.print("<form method='post'>");
pw.print("<input type='text' name='" + ATTR_TEST + "' value='");
if (test != null) {
pw.print(ResponseUtil.escapeXml(test));
}
pw.println("' class='input' size='50'>");
pw.println("&nbsp;&nbsp;<input type='submit' name='" + ATTR_SUBMIT
+ "' value='Resolve' class='submit'>");
pw.println("&nbsp;&nbsp;<input type='submit' name='" + ATTR_SUBMIT
+ "' value='Map' class='submit'>");
pw.print("</form>");
pw.print("</td>");
pw.println("</tr>");
if (msg != null) {
pw.println("<tr class='content'>");
pw.println("<td class='content'>&nbsp;</td>");
pw.print("<td class='content' colspan='2'>");
pw.print(ResponseUtil.escapeXml(msg));
pw.println("</td>");
pw.println("</tr>");
}
separatorHtml(pw);
dumpMapHtml(
pw,
"Resolver Map Entries",
"Lists the entries used by the ResourceResolver.resolve methods to map URLs to Resources",
mapEntries.getResolveMaps());
separatorHtml(pw);
dumpMapHtml(
pw,
"Mapping Map Entries",
"Lists the entries used by the ResourceResolver.map methods to map Resource Paths to URLs",
mapEntries.getMapMaps());
separatorHtml(pw);
dumpDTOsHtml(pw);
pw.println("</table>");
}
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
final String test = request.getParameter(ATTR_TEST);
String msg = null;
if (test != null && test.length() > 0) {
ResourceResolver resolver = null;
try {
// prepare the request for the resource resolver
HttpServletRequest helper = new ResolverRequest(request, test);
resolver = resolverFactory.getServiceResourceResolver(this.resolverFactory.getServiceUserAuthenticationInfo("console"));
// map or resolve as instructed
Object result;
if ("Map".equals(request.getParameter(ATTR_SUBMIT))) {
ResourceMapper mapper = resolver.adaptTo(ResourceMapper.class);
if (helper.getServerName() == null) {
result = mappingsToString(mapper.getAllMappings(helper.getPathInfo()));
} else {
result = mappingsToString(mapper.getAllMappings(helper.getPathInfo(), helper));
}
} else {
result = resolver.resolve(helper, helper.getPathInfo());
}
// set the result to render the result
msg = result.toString();
} catch (final Throwable t) {
// some error occurred, report it as a result
msg = "Test Failure: " + t;
} finally {
if (resolver != null) {
resolver.close();
}
}
}
// finally redirect
final String path = request.getContextPath() + request.getServletPath()
+ request.getPathInfo();
final String redirectTo;
if (msg == null) {
redirectTo = path;
} else {
redirectTo = path + '?' + PAR_MSG + '=' + encodeParam(msg) + '&'
+ PAR_TEST + '=' + encodeParam(test);
}
response.sendRedirect(redirectTo);
}
private static String mappingsToString(Collection<String> allMappings) {
if ( allMappings.size() == 0 )
return ""; // should not happen
if ( allMappings.size() == 1)
return allMappings.iterator().next();
StringBuilder out = new StringBuilder();
for ( Iterator<String> it = allMappings.iterator(); it.hasNext(); ) {
if ( out.length() == 0 ) {
out.append("Primary: ").append(it.next());
if ( it.hasNext() )
out.append(". Other candidates:");
}
else
out.append(' ').append(it.next()).append(',');
}
out.setCharAt(out.length() - 1, '.');
return out.toString();
}
private String encodeParam(final String value) {
try {
return URLEncoder.encode(value, "UTF-8");
} catch (UnsupportedEncodingException e) {
// should never happen
return value;
}
}
// ---------- ConfigurationPrinter
public void printConfiguration(final PrintWriter pw) {
dumpDTOsText(pw);
separatorText(pw);
final MapEntriesHandler mapEntries = resolverFactory.getMapEntries();
dumpMapText(pw, "Resolver Map Entries", mapEntries.getResolveMaps());
separatorText(pw);
dumpMapText(pw, "Mapping Map Entries", mapEntries.getMapMaps());
}
// ---------- internal
private void dumpMapHtml(PrintWriter pw, String title, String description,
Collection<MapEntry> list) {
titleHtml(pw, title, description);
pw.println("<tr class='content'>");
pw.println("<th class='content'>Pattern</th>");
pw.println("<th class='content'>Replacement</th>");
pw.println("<th class='content'>Redirect</th>");
pw.println("</tr>");
final Set<String> usedPatterns = new HashSet<String>();
for (final MapEntry entry : list) {
final String pattern = entry.getPattern();
pw.print("<tr class='content");
if (!usedPatterns.add(pattern)) {
pw.print(" duplicate");
}
pw.println("'>");
pw.println("<td class='content' style='vertical-align: top'>");
pw.print(ResponseUtil.escapeXml(pattern));
pw.print("</td>");
pw.print("<td class='content' style='vertical-align: top'>");
final String[] repls = entry.getRedirect();
for (final String repl : repls) {
pw.print(ResponseUtil.escapeXml(repl));
pw.print("<br/>");
}
pw.print("</td>");
pw.print("<td class='content' style='vertical-align: top'>");
if (entry.isInternal()) {
pw.print("internal");
} else {
pw.print("external: ");
pw.print(String.valueOf(entry.getStatus()));
}
pw.println("</td></tr>");
}
}
private void titleHtml(PrintWriter pw, String title, String description) {
pw.print("<tr class='content'>");
pw.print("<th colspan='3'class='content container'>");
pw.print(ResponseUtil.escapeXml(title));
pw.println("</th></tr>");
if (description != null) {
pw.print("<tr class='content'>");
pw.print("<td colspan='3'class='content'>");
pw.print(ResponseUtil.escapeXml(description));
pw.println("</th></tr>");
}
}
private void separatorHtml(PrintWriter pw) {
pw.print("<tr class='content'>");
pw.println("<td class='content' colspan='3'>&nbsp;</td>");
pw.println("</tr>");
}
private void dumpMapText(PrintWriter pw, String title,
Collection<MapEntry> list) {
pw.println(title);
final String format = "%25s%25s%15s\r\n";
pw.printf(format, "Pattern", "Replacement", "Redirect");
for (MapEntry entry : list) {
final List<String> redir = Arrays.asList(entry.getRedirect());
final String status = entry.isInternal() ? "internal"
: "external: " + entry.getStatus();
pw.printf(format, entry.getPattern(), redir, status);
}
}
private ServiceReference<ResourceProvider<?>> getServiceReference(final long id) {
try {
final Collection<ServiceReference<ResourceProvider>> refs = this.bundleContext.getServiceReferences(ResourceProvider.class,
"(" + Constants.SERVICE_ID + "=" + String.valueOf(id) + ")");
if ( refs != null && !refs.isEmpty() ) {
final ServiceReference rp = refs.iterator().next();
return rp;
}
} catch ( final InvalidSyntaxException ise) {
// ignore
}
return null;
}
private void dumpDTOsHtml(final PrintWriter pw) {
titleHtml(pw, "Resource Providers", "Lists all available and activate resource prodivers.");
pw.println("<tr class='content'>");
pw.println("<th class='content'>Provider</th>");
pw.println("<th class='content'>Path</th>");
pw.println("<th class='content'>Configuration</th>");
pw.println("</tr>");
final RuntimeDTO runtimeDTO = this.runtimeService.getRuntimeDTO();
for(final ResourceProviderDTO dto : runtimeDTO.providers) {
// get service reference
final ServiceReference<ResourceProvider<?>> ref = this.getServiceReference(dto.serviceId);
final StringBuilder sb = new StringBuilder();
if ( dto.name != null ) {
sb.append(dto.name);
sb.append(' ');
} else {
sb.append("<unnamed> ");
}
if ( ref != null ) {
sb.append("(serviceId = ");
sb.append(dto.serviceId);
sb.append(", bundleId = ");
sb.append(ref.getBundle().getBundleId());
sb.append(")");
}
pw.print("<tr class='content'>");
pw.print("<td class='content' style='vertical-align: top'>");
pw.print(ResponseUtil.escapeXml(sb.toString()));
pw.print("</td>");
pw.print("<td class='content' style='vertical-align: top'>");
pw.print(ResponseUtil.escapeXml(dto.path));
pw.print("</td>");
pw.print("<td class='content' style='vertical-align: top'>");
pw.print("auth=");
pw.print(dto.authType.name());
pw.print("<br/>");
pw.print("adaptable=");
pw.print(dto.adaptable);
pw.print("<br/>");
pw.print("attributable=");
pw.print(dto.attributable);
pw.print("<br/>");
pw.print("modifiable=");
pw.print(dto.modifiable);
pw.print("<br/>");
pw.print("refreshable=");
pw.print(dto.refreshable);
pw.print("<br/>");
pw.print("supportsQueryLanguage=");
pw.print(dto.supportsQueryLanguage);
pw.print("<br/>");
pw.print("useResourceAccessSecurity=");
pw.print(dto.useResourceAccessSecurity);
pw.println("</td></tr>");
}
if ( runtimeDTO.failedProviders.length > 0 ) {
titleHtml(pw, "Failed Resource Providers", "Lists all failed providers.");
pw.println("<tr class='content'>");
pw.println("<th class='content'>Provider</th>");
pw.println("<th class='content'>Path</th>");
pw.println("<th class='content'>Reason</th>");
pw.println("</tr>");
for(final ResourceProviderFailureDTO dto : runtimeDTO.failedProviders) {
// get service reference
final ServiceReference<ResourceProvider<?>> ref = this.getServiceReference(dto.serviceId);
final StringBuilder sb = new StringBuilder();
if ( dto.name != null ) {
sb.append(dto.name);
sb.append(' ');
} else {
sb.append("<unnamed> ");
}
if ( ref != null ) {
sb.append("(serviceId = ");
sb.append(dto.serviceId);
sb.append(", bundleId = ");
sb.append(ref.getBundle().getBundleId());
sb.append(")");
}
pw.print("<tr class='content'>");
pw.print("<td class='content' style='vertical-align: top'>");
pw.print(ResponseUtil.escapeXml(sb.toString()));
pw.print("</td>");
pw.print("<td class='content' style='vertical-align: top'>");
pw.print(ResponseUtil.escapeXml(dto.path));
pw.print("</td>");
pw.print("<td class='content' style='vertical-align: top'>");
pw.print(dto.reason.name());
pw.println("</td></tr>");
}
}
}
private void dumpDTOsText(final PrintWriter pw) {
pw.println("Resource Providers");
final String format = "%35s %25s %15s\r\n";
pw.printf(format, "Provider", "Path", "Configuration");
final RuntimeDTO runtimeDTO = this.runtimeService.getRuntimeDTO();
for(final ResourceProviderDTO dto : runtimeDTO.providers) {
// get service reference
final ServiceReference<ResourceProvider<?>> ref = this.getServiceReference(dto.serviceId);
final StringBuilder sb = new StringBuilder();
if ( dto.name != null ) {
sb.append(dto.name);
sb.append(' ');
} else {
sb.append("<unnamed> ");
}
if ( ref != null ) {
sb.append("(serviceId = ");
sb.append(dto.serviceId);
sb.append(", bundleId = ");
sb.append(ref.getBundle().getBundleId());
sb.append(")");
}
final StringBuilder config = new StringBuilder();
config.append("auth=");
config.append(dto.authType.name());
config.append(", adaptable=");
config.append(dto.adaptable);
config.append(", attributable=");
config.append(dto.attributable);
config.append(", modifiable=");
config.append(dto.modifiable);
config.append(", refreshable=");
config.append(dto.refreshable);
config.append(", supportsQueryLanguage=");
config.append(dto.supportsQueryLanguage);
config.append(", useResourceAccessSecurity=");
config.append(dto.useResourceAccessSecurity);
pw.printf(format, sb.toString(), dto.path, config.toString());
}
pw.println();
if ( runtimeDTO.failedProviders.length > 0 ) {
pw.println("Failed Resource Providers");
pw.printf(format, "Provider", "Path", "Reason");
for(final ResourceProviderFailureDTO dto : runtimeDTO.failedProviders) {
// get service reference
final ServiceReference<ResourceProvider<?>> ref = this.getServiceReference(dto.serviceId);
final StringBuilder sb = new StringBuilder();
if ( dto.name != null ) {
sb.append(dto.name);
sb.append(' ');
} else {
sb.append("<unnamed> ");
}
if ( ref != null ) {
sb.append("(serviceId = ");
sb.append(dto.serviceId);
sb.append(", bundleId = ");
sb.append(ref.getBundle().getBundleId());
sb.append(")");
}
pw.printf(format, sb.toString(), dto.path, dto.reason.name());
}
pw.println();
}
}
private void separatorText(PrintWriter pw) {
pw.println();
}
/**
* Method to retrieve static resources from this bundle.
*/
@SuppressWarnings("unused")
private URL getResource(final String path) {
if (path.startsWith("/jcrresolver/res/ui/")) {
return this.getClass().getResource(path.substring(12));
}
return null;
}
private static class ResolverRequest extends HttpServletRequestWrapper {
private final URI uri;
public ResolverRequest(HttpServletRequest request, String uriString)
throws URIException {
super(request);
uri = new URI(uriString, false);
}
@Override
public String getScheme() {
return uri.getScheme();
}
@Override
public String getServerName() {
try {
return uri.getHost();
} catch (URIException ue) {
return null;
}
}
@Override
public int getServerPort() {
return uri.getPort();
}
@Override
public String getPathInfo() {
try {
return uri.getPath();
} catch (URIException ue) {
return "";
}
}
}
}