| /* |
| * 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(" <input type='submit' name='" + ATTR_SUBMIT |
| + "' value='Resolve' class='submit'>"); |
| pw.println(" <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'> </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'> </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 ""; |
| } |
| } |
| } |
| |
| } |