blob: 542a3c54b159e9b59b2a50f0b4916c512a33723b [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.servlets.resolver.internal.console;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicReference;
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.lang3.StringUtils;
import org.apache.sling.api.request.RequestPathInfo;
import org.apache.sling.api.request.ResponseUtil;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceMetadata;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.resource.SyntheticResource;
import org.apache.sling.api.scripting.SlingScript;
import org.apache.sling.api.servlets.OptingServlet;
import org.apache.sling.engine.impl.request.SlingRequestPathInfo;
import org.apache.sling.serviceusermapping.ServiceUserMapped;
import org.apache.sling.servlets.resolver.internal.ResolverConfig;
import org.apache.sling.servlets.resolver.internal.SlingServletResolver;
import org.apache.sling.servlets.resolver.internal.helper.ResourceCollector;
import org.apache.sling.servlets.resolver.internal.resolution.ResolutionCache;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
@SuppressWarnings("serial")
@Component(service = {Servlet.class},
configurationPid = ResolverConfig.PID,
property = {
Constants.SERVICE_DESCRIPTION + "=Sling Servlet Resolver Web Console Plugin",
Constants.SERVICE_VENDOR + "=The Apache Software Foundation",
"felix.webconsole.label=servletresolver",
"felix.webconsole.title=Sling Servlet Resolver",
"felix.webconsole.css=/servletresolver/res/ui/styles.css",
"felix.webconsole.category=Sling"
})
public class WebConsolePlugin extends HttpServlet {
private static final String PARAMETER_URL = "url";
private static final String PARAMETER_METHOD = "method";
private static final String SERVICE_USER_CONSOLE = "console";
@Reference(target="("+ServiceUserMapped.SUBSERVICENAME+"=" + SERVICE_USER_CONSOLE + ")")
private ServiceUserMapped consoleServiceUserMapped; // NOSONAR
@Reference
private ResourceResolverFactory resourceResolverFactory; // NOSONAR
@Reference
private ResolutionCache resolutionCache; // NOSONAR
/**
* The allowed execution paths.
*/
private AtomicReference<String[]> executionPaths = new AtomicReference<>(); // NOSONAR
/**
* The default extensions
*/
private AtomicReference<String[]> defaultExtensions = new AtomicReference<>(); // NOSONAR
/**
* Activate this component.
*/
@Activate
@Modified
protected void activate(final ResolverConfig config) {
this.executionPaths.set(SlingServletResolver.getExecutionPaths(config.servletresolver_paths()));
this.defaultExtensions.set(config.servletresolver_defaultExtensions());
}
@Override
protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
final String url = request.getParameter(PARAMETER_URL);
final RequestPathInfo requestPathInfo = getRequestPathInfo(url);
String method = request.getParameter(PARAMETER_METHOD);
if (StringUtils.isBlank(method)) {
method = "GET";
}
final String CONSOLE_PATH_WARNING =
"<em>"
+ "Note that in a real Sling request, the path might vary depending on the existence of"
+ " resources that partially match it."
+ "<br/>This utility does not take this into account and uses the first dot to split"
+ " between path and selectors/extension."
+ "<br/>As a workaround, you can replace dots with underline characters, for example, when testing such an URL."
+ "</em>";
try (final ResourceResolver resourceResolver = resourceResolverFactory.getServiceResourceResolver(Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, (Object)SERVICE_USER_CONSOLE))) {
final PrintWriter pw = response.getWriter();
pw.print("<form method='get'>");
pw.println("<table class='content' cellpadding='0' cellspacing='0' width='100%'>");
titleHtml(
pw,
"Servlet Resolver Test",
"To check which servlet is responsible for rendering a response, enter a request path into " +
"the field and click 'Resolve' to resolve it.");
tr(pw);
tdLabel(pw, "URL");
tdContent(pw);
pw.print("<input type='text' name='");
pw.print(PARAMETER_URL);
pw.print("' value='");
if ( url != null ) {
pw.print(ResponseUtil.escapeXml(url));
}
pw.println("' class='input' size='50'>");
closeTd(pw);
closeTr(pw);
closeTr(pw);
tr(pw);
tdLabel(pw, "Method");
tdContent(pw);
pw.print("<select name='");
pw.print(PARAMETER_METHOD);
pw.println("'>");
pw.println("<option value='GET'>GET</option>");
pw.println("<option value='POST'>POST</option>");
pw.println("</select>");
pw.println("&nbsp;&nbsp;<input type='submit' value='Resolve' class='submit'>");
closeTd(pw);
closeTr(pw);
if (StringUtils.isNotBlank(url)) {
tr(pw);
tdLabel(pw, "Decomposed URL");
tdContent(pw);
pw.println("<dl>");
pw.println("<dt>Path</dt>");
dd(pw);
pw.print(ResponseUtil.escapeXml(requestPathInfo.getResourcePath()));
pw.print("<br/>");
pw.print(CONSOLE_PATH_WARNING);
closeDd(pw);
pw.println("<dt>Selectors</dt>");
dd(pw);
if (requestPathInfo.getSelectors().length == 0) {
pw.print("&lt;none&gt;");
} else {
pw.print("[");
pw.print(ResponseUtil.escapeXml(StringUtils.join(requestPathInfo.getSelectors(), ", ")));
pw.print("]");
}
closeDd(pw);
pw.println("<dt>Extension</dt>");
dd(pw);
pw.print(ResponseUtil.escapeXml(requestPathInfo.getExtension()));
closeDd(pw);
pw.println("</dl>");
closeDd(pw);
pw.println("<dt>Suffix</dt>");
dd(pw);
pw.print(ResponseUtil.escapeXml(requestPathInfo.getSuffix()));
closeDd(pw);
pw.println("</dl>");
closeTd(pw);
closeTr(pw);
}
if (StringUtils.isNotBlank(requestPathInfo.getResourcePath())) {
final Collection<Resource> servlets;
Resource resource = resourceResolver.resolve(requestPathInfo.getResourcePath());
if (resource.adaptTo(Servlet.class) != null) {
servlets = Collections.singleton(resource);
} else {
final ResourceCollector locationUtil = ResourceCollector.create(
resource,
requestPathInfo.getExtension(),
executionPaths.get(),
defaultExtensions.get(),
method,
requestPathInfo.getSelectors());
servlets = locationUtil.getServlets(resourceResolver, resolutionCache.getScriptEngineExtensions());
}
tr(pw);
tdLabel(pw, "Candidates");
tdContent(pw);
if (servlets == null || servlets.isEmpty()) {
pw.println("Could not find a suitable servlet for this request!");
} else {
// check for non-existing resources
if (ResourceUtil.isNonExistingResource(resource)) {
pw.println("The resource given by path '");
pw.println(ResponseUtil.escapeXml(resource.getPath()));
pw.println("' does not exist. Therefore no resource type could be determined!<br/>");
}
pw.print("Candidate servlets and scripts in order of preference for method ");
pw.print(ResponseUtil.escapeXml(method));
pw.println(":<br/>");
pw.println("<ol class='servlets'>");
outputServlets(pw, servlets.iterator());
pw.println("</ol>");
}
closeTd(pw);
closeTr(pw);
}
pw.println("</table>");
pw.print("</form>");
} catch (final LoginException e) {
throw new ServletException(e);
}
}
private void tdContent(final PrintWriter pw) {
pw.print("<td class='content' colspan='2'>");
}
private void closeTd(final PrintWriter pw) {
pw.print("</td>");
}
private void dd(final PrintWriter pw) {
pw.println("<dd>");
}
private void closeDd(final PrintWriter pw) {
pw.print("</dd>");
}
@SuppressWarnings("unused")
private URL getResource(final String path) {
if (path.startsWith("/servletresolver/res/ui")) {
return this.getClass().getResource(path.substring(16));
} else {
return null;
}
}
private void closeTr(final PrintWriter pw) {
pw.println("</tr>");
}
private void tdLabel(final PrintWriter pw, final String label) {
pw.print("<td class='content'>");
pw.print(ResponseUtil.escapeXml(label));
pw.println("</td>");
}
private void tr(final PrintWriter pw) {
pw.println("<tr class='content'>");
}
private void outputServlets(final PrintWriter pw, final Iterator<Resource> iterator) {
while (iterator.hasNext()) {
Resource candidateResource = iterator.next();
Servlet candidate = candidateResource.adaptTo(Servlet.class);
if (candidate != null) {
final boolean allowed = SlingServletResolver.isPathAllowed(candidateResource.getPath(), this.executionPaths.get());
pw.print("<li>");
if ( !allowed ) {
pw.print("<del>");
}
if (candidate instanceof SlingScript) {
pw.print(ResponseUtil.escapeXml(candidateResource.getPath()));
} else {
final boolean isOptingServlet = candidate instanceof OptingServlet;
pw.print(ResponseUtil.escapeXml((candidate.getClass().getName())));
if ( isOptingServlet ) {
pw.print(" (OptingServlet)");
}
}
if ( !allowed ) {
pw.print("</del>");
}
pw.println("</li>");
}
}
}
private void titleHtml(final PrintWriter pw, final String title, final String description) {
tr(pw);
pw.print("<th colspan='3' class='content container'>");
pw.print(ResponseUtil.escapeXml(title));
pw.println("</th>");
closeTr(pw);
if (description != null) {
tr(pw);
pw.print("<td colspan='3' class='content'>");
pw.print(ResponseUtil.escapeXml(description));
pw.println("</th>");
closeTr(pw);
}
}
public static RequestPathInfo getRequestPathInfo(String urlString) {
if(urlString == null) {
urlString = "";
}
// For the path, take everything up to the first dot
String fullPath = urlString;
if(urlString.contains("http")) {
try {
fullPath = new URL(urlString).getPath();
} catch(MalformedURLException ignore) {
// ignored
}
}
final int firstDot = fullPath.indexOf(".");
final ResourceMetadata metadata = new ResourceMetadata();
final Resource r = new SyntheticResource(null, metadata, null); // NOSONAR
metadata.setResolutionPath(firstDot < 0 ? fullPath : fullPath.substring(0, firstDot));
metadata.setResolutionPathInfo(firstDot < 0 ? null : fullPath.substring(firstDot));
return new SlingRequestPathInfo(r);
}
}