Merge pull request #19 from angulito/SLING-10644-return-servlet-resolver-information-in-json-format
Sling 10644 return servlet resolver information in json format
diff --git a/pom.xml b/pom.xml
index 52b30d4..bc2f799 100644
--- a/pom.xml
+++ b/pom.xml
@@ -431,6 +431,18 @@
<version>1.0</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>com.jayway.jsonpath</groupId>
+ <artifactId>json-path</artifactId>
+ <version>2.6.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.jayway.jsonpath</groupId>
+ <artifactId>json-path-assert</artifactId>
+ <version>2.6.0</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/src/main/java/org/apache/sling/servlets/resolver/internal/console/WebConsolePlugin.java b/src/main/java/org/apache/sling/servlets/resolver/internal/console/WebConsolePlugin.java
index 542a3c5..a2a9cde 100644
--- a/src/main/java/org/apache/sling/servlets/resolver/internal/console/WebConsolePlugin.java
+++ b/src/main/java/org/apache/sling/servlets/resolver/internal/console/WebConsolePlugin.java
@@ -18,21 +18,7 @@
*/
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.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.request.RequestPathInfo;
import org.apache.sling.api.request.ResponseUtil;
@@ -57,6 +43,28 @@
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * If the servlet request path ends with .json, the information is returned in JSON format.
+ * Otherwise, an HTML code is returned.
+ */
@SuppressWarnings("serial")
@Component(service = {Servlet.class},
configurationPid = ResolverConfig.PID,
@@ -74,6 +82,13 @@
private static final String PARAMETER_METHOD = "method";
private static final String SERVICE_USER_CONSOLE = "console";
+ private static final String CONSOLE_PATH_WARNING =
+ "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 a URL.";
@Reference(target="("+ServiceUserMapped.SUBSERVICENAME+"=" + SERVICE_USER_CONSOLE + ")")
private ServiceUserMapped consoleServiceUserMapped; // NOSONAR
@@ -113,139 +128,255 @@
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>";
-
+ String requestURI = request.getRequestURI();
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(" <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("<none>");
- } else {
- pw.print("[");
- pw.print(ResponseUtil.escapeXml(StringUtils.join(requestPathInfo.getSelectors(), ", ")));
- pw.print("]");
+ if (requestURI.endsWith("json")) {
+ pw.println("{");
+ if (StringUtils.isNotBlank(url)) {
+ printJSONDecomposedURLElement(pw, requestPathInfo);
}
- 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());
+ if (StringUtils.isNotBlank(requestPathInfo.getResourcePath())) {
+ printJSONCandidatesElement(pw, resourceResolver, requestPathInfo, method);
}
- tr(pw);
- tdLabel(pw, "Candidates");
- tdContent(pw);
+ pw.printf(" \"warningMsg\" : \"%s\",%n", CONSOLE_PATH_WARNING.replace("<br/>", ""));
+ pw.printf(" \"method\" : \"%s\"%n", StringEscapeUtils.escapeJson(method));
+ pw.print("}");
- 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/>");
+ response.setContentType("application/json");
+ } else {
+ printHTMLInputElements(pw, url);
+ if (StringUtils.isNotBlank(url)) {
+ printHTMLDecomposedURLElement(pw, requestPathInfo);
+ }
+
+ if (StringUtils.isNotBlank(requestPathInfo.getResourcePath())) {
+ Resource resource = resourceResolver.resolve(requestPathInfo.getResourcePath());
+ final Collection<Resource> servlets = resolveServlets(resourceResolver, requestPathInfo, resource,
+ method);
+
+ 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'>");
+ outputHTMLServlets(pw, servlets.iterator());
+ pw.println("</ol>");
}
- 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);
}
- closeTd(pw);
- closeTr(pw);
- }
- pw.println("</table>");
- pw.print("</form>");
+ pw.println("</table>");
+ pw.print("</form>");
+ }
} catch (final LoginException e) {
throw new ServletException(e);
}
}
+ /**
+ * Format an array.
+ */
+ private String formatArrayAsJSON(final String[] array) {
+ if ( array == null || array.length == 0 ) {
+ return "[]";
+ }
+ final StringBuilder sb = new StringBuilder("[");
+ boolean first = true;
+ for(final String s : array) {
+ if ( !first ) {
+ sb.append(", ");
+ }
+ first = false;
+ sb.append("\"");
+ sb.append(StringEscapeUtils.escapeJson(s));
+ sb.append("\"");
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ private Map<String, List<String>> getAllowedAndDeniedServlets(Collection<Resource> servlets) {
+ List<String> allowedServlets = new ArrayList<>();
+ List<String> deniedServlets = new ArrayList<>();
+ for (Resource candidateResource : servlets) {
+ Servlet candidate = candidateResource.adaptTo(Servlet.class);
+ if (candidate != null) {
+ final boolean allowed = SlingServletResolver.isPathAllowed(candidateResource.getPath(),
+ this.executionPaths.get());
+
+ String finalCandidate;
+ if (candidate instanceof SlingScript) {
+ finalCandidate = candidateResource.getPath();
+ } else {
+ final boolean isOptingServlet = candidate instanceof OptingServlet;
+ finalCandidate = candidate.getClass().getName();
+ if (isOptingServlet) {
+ finalCandidate += " (OptingServlet)";
+ }
+ }
+
+ if (allowed) {
+ allowedServlets.add(finalCandidate);
+ } else {
+ deniedServlets.add(finalCandidate);
+ }
+ }
+ }
+
+ Map<String, List<String>> result = new HashMap<>();
+ result.put("allowed", allowedServlets);
+ result.put("denied", deniedServlets);
+
+ return result;
+ }
+
+ private void printJSONDecomposedURLElement(PrintWriter pw, RequestPathInfo requestPathInfo) {
+ pw.println(" \"decomposedURL\" : {");
+ pw.printf(" \"path\" : \"%s\",%n",
+ StringEscapeUtils.escapeJson(StringUtils.defaultIfEmpty(requestPathInfo.getResourcePath(), "")));
+ pw.printf(" \"extension\" : \"%s\",%n",
+ StringEscapeUtils.escapeJson(StringUtils.defaultIfEmpty(requestPathInfo.getExtension(), "")));
+ pw.printf(" \"selectors\" : %s,%n",
+ StringUtils.defaultIfEmpty(formatArrayAsJSON(requestPathInfo.getSelectors()), ""));
+ pw.printf(" \"suffix\" : \"%s\"%n",
+ StringEscapeUtils.escapeJson(StringUtils.defaultIfEmpty(requestPathInfo.getSuffix(), "")));
+ pw.println(" },");
+ }
+
+ private void printJSONCandidatesElement(PrintWriter pw, ResourceResolver resourceResolver,
+ RequestPathInfo requestPathInfo, String method) {
+ Resource resource = resourceResolver.resolve(requestPathInfo.getResourcePath());
+ final Collection<Resource> servlets = resolveServlets(resourceResolver, requestPathInfo, resource,
+ method);
+ pw.println(" \"candidates\" : {");
+ if (servlets != null) {
+ // check for non-existing resources
+ if (ResourceUtil.isNonExistingResource(resource)) {
+ pw.printf(" \"errorMsg\" : \"%s\",%n", String.format("The resource given by path " +
+ "'%s' does not exist. Therefore no " +
+ "resource type could be determined!", StringEscapeUtils.escapeJson(resource.getPath())));
+ }
+
+ Map<String, List<String>> allowedAndDeniedServlets = getAllowedAndDeniedServlets(servlets);
+ List<String> allowedServlets = allowedAndDeniedServlets.getOrDefault("allowed", new ArrayList<>());
+ List<String> deniedServlets = allowedAndDeniedServlets.getOrDefault("denied", new ArrayList<>());
+ pw.printf(" \"allowedServlets\" : %s,%n",
+ formatArrayAsJSON(allowedServlets.toArray(new String[0])));
+ pw.printf(" \"deniedServlets\" : %s%n",
+ formatArrayAsJSON(deniedServlets.toArray(new String[0])));
+ }
+ pw.print(" },");
+ }
+
+ private void printHTMLInputElements(PrintWriter pw, String url) {
+ 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(" <input type='submit' value='Resolve' class='submit'>");
+
+ closeTd(pw);
+ closeTr(pw);
+ }
+
+ private void printHTMLDecomposedURLElement(PrintWriter pw, RequestPathInfo requestPathInfo) {
+ 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(String.format("<em>%s</em>", CONSOLE_PATH_WARNING));
+ closeDd(pw);
+ pw.println("<dt>Selectors</dt>");
+ dd(pw);
+ if (requestPathInfo.getSelectors().length == 0) {
+ pw.print("<none>");
+ } 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);
+ }
+
+ private Collection<Resource> resolveServlets(ResourceResolver resourceResolver, RequestPathInfo requestPathInfo,
+ Resource resource, String method) {
+ final Collection<Resource> servlets;
+ 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());
+ }
+
+ return servlets;
+ }
+
private void tdContent(final PrintWriter pw) {
pw.print("<td class='content' colspan='2'>");
}
@@ -284,29 +415,29 @@
pw.println("<tr class='content'>");
}
- private void outputServlets(final PrintWriter pw, final Iterator<Resource> iterator) {
+ private void outputHTMLServlets(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>");
- }
+ String candidateStr;
if (candidate instanceof SlingScript) {
- pw.print(ResponseUtil.escapeXml(candidateResource.getPath()));
+ candidateStr = ResponseUtil.escapeXml(candidateResource.getPath());
} else {
final boolean isOptingServlet = candidate instanceof OptingServlet;
- pw.print(ResponseUtil.escapeXml((candidate.getClass().getName())));
+ candidateStr = ResponseUtil.escapeXml((candidate.getClass().getName()));
if ( isOptingServlet ) {
- pw.print(" (OptingServlet)");
+ candidateStr +=" (OptingServlet)";
}
}
if ( !allowed ) {
- pw.print("</del>");
+ pw.print("<del>" + candidateStr + "</del>");
+ } else {
+ pw.print(candidateStr);
}
pw.println("</li>");
}
diff --git a/src/test/java/org/apache/sling/servlets/resolver/internal/console/WebConsolePluginTest.java b/src/test/java/org/apache/sling/servlets/resolver/internal/console/WebConsolePluginTest.java
new file mode 100644
index 0000000..61b886f
--- /dev/null
+++ b/src/test/java/org/apache/sling/servlets/resolver/internal/console/WebConsolePluginTest.java
@@ -0,0 +1,338 @@
+/*
+ * 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 org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.servlets.resolver.internal.ResolverConfig;
+import org.apache.sling.servlets.resolver.internal.resolution.ResolutionCache;
+import org.hamcrest.CoreMatchers;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import javax.json.Json;
+import javax.servlet.Servlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+public class WebConsolePluginTest {
+ @Mock
+ private HttpServletRequest request;
+ @Mock
+ private HttpServletResponse response;
+ @Mock
+ private ResourceResolverFactory resourceResolverFactory;
+ @Mock
+ private ResolutionCache resolutionCache;
+ @InjectMocks
+ private WebConsolePlugin webConsolePlugin;
+
+ private StringWriter stringWriter;
+ private PrintWriter writer;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.openMocks(this);
+ ResolverConfig resolverConfig = Mockito.mock(ResolverConfig.class);
+ Mockito.when(resolverConfig.servletresolver_defaultExtensions()).thenReturn(new String[]{"html"});
+ Mockito.when(resolverConfig.servletresolver_paths()).thenReturn(new String[]{"/path"});
+ webConsolePlugin.activate(resolverConfig);
+ // mock http responses
+ stringWriter = new StringWriter();
+ writer = new PrintWriter(stringWriter);
+ Mockito.when(response.getWriter()).thenReturn(writer);
+ }
+
+ @After
+ public void closeUp() throws Exception {
+ MockitoAnnotations.openMocks(this).close();
+ }
+
+ @Test
+ public void printJsonFormatFull() throws Exception {
+ // Mock resource resolver
+ ResourceResolver resourceResolver = Mockito.mock(ResourceResolver.class);
+ Resource resource = Mockito.mock(Resource.class);
+ Servlet servlet = Mockito.mock(Servlet.class);
+ Mockito.when(resource.adaptTo(Servlet.class)).thenReturn(servlet);
+ Mockito.when(resource.getPath()).thenReturn("/path");
+ Mockito.when(resourceResolver.resolve(Mockito.anyString())).thenReturn(resource);
+ Mockito.when(resourceResolverFactory.getServiceResourceResolver(Mockito.anyMap())).thenReturn(resourceResolver);
+
+ // Mock request calls
+ Mockito.when(request.getParameter("url")).thenReturn("/test.1.json");
+ Mockito.when(request.getParameter("method")).thenReturn("GET");
+
+ // JSON output
+ Mockito.when(request.getRequestURI()).thenReturn("/path/servletresolver.json");
+
+ webConsolePlugin.service(request, response);
+
+ Mockito.verify(response).setContentType("application/json");
+
+ String jsonString = stringWriter.toString();
+ String json = Json.createReader(new StringReader(jsonString)).readObject().toString();
+
+ Map<String, Object> expectedJsonPaths = new HashMap(){{
+ put("$.candidates", null);
+ put("$.candidates.allowedServlets.length()", 1);
+ put("$.candidates.deniedServlets.length()", 0);
+ put("$.decomposedURL", null);
+ put("$.decomposedURL.path", "/test");
+ put("$.decomposedURL.extension", "json");
+ put("$.decomposedURL.selectors", Arrays.asList("1"));
+ put("$.decomposedURL.suffix", "");
+ put("$.method", "GET");
+ }};
+
+ expectedJsonPaths.forEach((k, v) -> {
+ if (v != null) {
+ assertThat(json, hasJsonPath(k, equalTo(v)));
+ } else {
+ assertThat(json, hasJsonPath(k));
+ }
+ });
+ }
+
+ @Test
+ public void printJsonFormatEmptyURL() throws Exception {
+ // Mock resource resolver
+ ResourceResolver resourceResolver = Mockito.mock(ResourceResolver.class);
+ Resource resource = Mockito.mock(Resource.class);
+ Mockito.when(resource.adaptTo(Servlet.class)).thenReturn(null);
+ Mockito.when(resourceResolver.resolve(Mockito.anyString())).thenReturn(resource);
+ Mockito.when(resourceResolverFactory.getServiceResourceResolver(Mockito.anyMap())).thenReturn(resourceResolver);
+
+ // Mock request calls
+ Mockito.when(request.getParameter("url")).thenReturn("");
+ Mockito.when(request.getParameter("method")).thenReturn("GET");
+
+ // JSON output
+ Mockito.when(request.getRequestURI()).thenReturn("/path/servletresolver.json");
+
+ webConsolePlugin.service(request, response);
+
+ Mockito.verify(response).setContentType("application/json");
+
+ String jsonString = stringWriter.toString();
+ String json = Json.createReader(new StringReader(jsonString)).readObject().toString();
+
+ Map<String, Object> expectedJsonPaths = new HashMap(){{
+ put("$.method", "GET");
+ }};
+
+ expectedJsonPaths.forEach((k, v) -> {
+ if (v != null) {
+ assertThat(json, hasJsonPath(k, equalTo(v)));
+ } else {
+ assertThat(json, hasJsonPath(k));
+ }
+ });
+ }
+
+ @Test
+ public void printJsonFormatDenyPaths() throws Exception {
+ // Mock resource resolver
+ ResourceResolver resourceResolver = Mockito.mock(ResourceResolver.class);
+ Resource resource = Mockito.mock(Resource.class);
+ Servlet servlet = Mockito.mock(Servlet.class);
+ Mockito.when(resource.adaptTo(Servlet.class)).thenReturn(servlet);
+ Mockito.when(resource.getPath()).thenReturn("/denied");
+ Mockito.when(resourceResolver.resolve(Mockito.anyString())).thenReturn(resource);
+ Mockito.when(resourceResolverFactory.getServiceResourceResolver(Mockito.anyMap())).thenReturn(resourceResolver);
+
+ // Mock request calls
+ Mockito.when(request.getParameter("url")).thenReturn("/denied");
+ Mockito.when(request.getParameter("method")).thenReturn("GET");
+
+ // JSON output
+ Mockito.when(request.getRequestURI()).thenReturn("/path/servletresolver.json");
+
+ webConsolePlugin.service(request, response);
+
+ Mockito.verify(response).setContentType("application/json");
+
+ String jsonString = stringWriter.toString();
+ String json = Json.createReader(new StringReader(jsonString)).readObject().toString();
+
+ Map<String, Object> expectedJsonPaths = new HashMap(){{
+ put("$.candidates", null);
+ put("$.candidates.allowedServlets.length()", 0);
+ put("$.candidates.deniedServlets.length()", 1);
+ }};
+
+ expectedJsonPaths.forEach((k, v) -> {
+ if (v != null) {
+ assertThat(json, hasJsonPath(k, equalTo(v)));
+ } else {
+ assertThat(json, hasJsonPath(k));
+ }
+ });
+ }
+
+ @Test
+ public void printHTMLFormatFull() throws Exception {
+ // Mock resource resolver
+ ResourceResolver resourceResolver = Mockito.mock(ResourceResolver.class);
+ Resource resource = Mockito.mock(Resource.class);
+ Servlet servlet = Mockito.mock(Servlet.class);
+ Mockito.when(resource.adaptTo(Servlet.class)).thenReturn(servlet);
+ Mockito.when(resourceResolver.resolve(Mockito.anyString())).thenReturn(resource);
+ Mockito.when(resourceResolverFactory.getServiceResourceResolver(Mockito.anyMap())).thenReturn(resourceResolver);
+
+ // Mock request calls
+ Mockito.when(request.getParameter("url")).thenReturn("/test.1.json");
+ Mockito.when(request.getParameter("method")).thenReturn("GET");
+
+ // HTML output
+ Mockito.when(request.getRequestURI()).thenReturn("/path/servletresolver");
+
+ webConsolePlugin.service(request, response);
+
+ String htmlString = stringWriter.toString();
+
+ final String expectedInputHTML = "<tr class='content'>\n" +
+ "<td class='content'>URL</td>\n" +
+ "<td class='content' colspan='2'><input type='text' name='url' value='/test.1.json' class='input' " +
+ "size='50'>\n" +
+ "</td></tr>\n" +
+ "</tr>\n" +
+ "<tr class='content'>\n" +
+ "<td class='content'>Method</td>\n" +
+ "<td class='content' colspan='2'><select name='method'>\n" +
+ "<option value='GET'>GET</option>\n" +
+ "<option value='POST'>POST</option>\n" +
+ "</select>\n" +
+ " <input type='submit' value='Resolve' class='submit'>\n" +
+ "</td></tr>";
+ assertThat(htmlString, CoreMatchers.containsString(expectedInputHTML));
+
+ final String expectedDecomposedURLHTML = "<tr class='content'>\n" +
+ "<td class='content'>Decomposed URL</td>\n" +
+ "<td class='content' colspan='2'><dl>\n" +
+ "<dt>Path</dt>\n" +
+ "<dd>\n" +
+ "/test<br/><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 a URL.</em></dd><dt>Selectors</dt>\n" +
+ "<dd>\n" +
+ "[1]</dd><dt>Extension</dt>\n" +
+ "<dd>\n" +
+ "json</dd></dl>\n" +
+ "</dd><dt>Suffix</dt>\n" +
+ "<dd>\n" +
+ "null</dd></dl>\n" +
+ "</td></tr>";
+ assertThat(htmlString, CoreMatchers.containsString(expectedDecomposedURLHTML));
+
+ final String expectedCandidatesHTML = "<tr class='content'>\n" +
+ "<td class='content'>Candidates</td>\n" +
+ "<td class='content' colspan='2'>Candidate servlets and scripts in order of preference for method " +
+ "GET:<br/>\n" +
+ "<ol class='servlets'>\n";
+ assertThat(htmlString, CoreMatchers.containsString(expectedCandidatesHTML));
+ }
+
+ @Test
+ public void printHTMLFormatEmptyURL() throws Exception {
+ // Mock resource resolver
+ ResourceResolver resourceResolver = Mockito.mock(ResourceResolver.class);
+ Resource resource = Mockito.mock(Resource.class);
+ Mockito.when(resource.adaptTo(Servlet.class)).thenReturn(null);
+ Mockito.when(resourceResolver.resolve(Mockito.anyString())).thenReturn(resource);
+ Mockito.when(resourceResolverFactory.getServiceResourceResolver(Mockito.anyMap())).thenReturn(resourceResolver);
+
+ // Mock request calls
+ Mockito.when(request.getParameter("url")).thenReturn("");
+ Mockito.when(request.getParameter("method")).thenReturn("GET");
+
+ // HTML output
+ Mockito.when(request.getRequestURI()).thenReturn("/path/servletresolver");
+
+ webConsolePlugin.service(request, response);
+
+ String htmlString = stringWriter.toString();
+ assertEquals("<form method='get'><table class='content' cellpadding='0' cellspacing='0' width='100%'>\n" +
+ "<tr class='content'>\n" +
+ "<th colspan='3' class='content container'>Servlet Resolver Test</th>\n" +
+ "</tr>\n" +
+ "<tr class='content'>\n" +
+ "<td colspan='3' class='content'>To check which servlet is responsible for rendering a response, " +
+ "enter a request path into the field and click 'Resolve' to resolve it.</th>\n" +
+ "</tr>\n" +
+ "<tr class='content'>\n" +
+ "<td class='content'>URL</td>\n" +
+ "<td class='content' colspan='2'><input type='text' name='url' value='' class='input' size='50'>\n" +
+ "</td></tr>\n" +
+ "</tr>\n" +
+ "<tr class='content'>\n" +
+ "<td class='content'>Method</td>\n" +
+ "<td class='content' colspan='2'><select name='method'>\n" +
+ "<option value='GET'>GET</option>\n" +
+ "<option value='POST'>POST</option>\n" +
+ "</select>\n" +
+ " <input type='submit' value='Resolve' class='submit'>\n" +
+ "</td></tr>\n" +
+ "</table>\n" +
+ "</form>", htmlString);
+ }
+
+ @Test
+ public void printHTMLFormatDenyPaths() throws Exception {
+ // Mock resource resolver
+ ResourceResolver resourceResolver = Mockito.mock(ResourceResolver.class);
+ Resource resource = Mockito.mock(Resource.class);
+ Servlet servlet = Mockito.mock(Servlet.class);
+ Mockito.when(resource.adaptTo(Servlet.class)).thenReturn(servlet);
+ Mockito.when(resource.getPath()).thenReturn("/denied");
+ Mockito.when(resourceResolver.resolve(Mockito.anyString())).thenReturn(resource);
+ Mockito.when(resourceResolverFactory.getServiceResourceResolver(Mockito.anyMap())).thenReturn(resourceResolver);
+
+ // Mock request calls
+ Mockito.when(request.getParameter("url")).thenReturn("/denied");
+ Mockito.when(request.getParameter("method")).thenReturn("GET");
+
+ // HTML output
+ Mockito.when(request.getRequestURI()).thenReturn("/path/servletresolver");
+ webConsolePlugin.service(request, response);
+
+ String htmlString = stringWriter.toString();
+ final String expectedDeniedElement = "<ol class='servlets'>\n" +
+ "<li><del>";
+ assertThat(htmlString, CoreMatchers.containsString(expectedDeniedElement));
+ }
+}