| /* |
| * 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.get.impl.helpers; |
| |
| import java.io.IOException; |
| import java.io.StringWriter; |
| |
| import javax.json.Json; |
| import javax.json.stream.JsonGenerator; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.sling.api.SlingException; |
| import org.apache.sling.api.SlingHttpServletRequest; |
| import org.apache.sling.api.SlingHttpServletResponse; |
| import org.apache.sling.api.resource.Resource; |
| import org.apache.sling.api.resource.ResourceNotFoundException; |
| import org.apache.sling.api.resource.ResourceUtil; |
| import org.apache.sling.servlets.get.impl.util.JsonToText; |
| import org.apache.sling.servlets.get.impl.util.ResourceTraversor; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * The <code>JsonRendererServlet</code> renders the current resource in JSON |
| * on behalf of the {@link org.apache.sling.servlets.get.impl.DefaultGetServlet}. |
| */ |
| public class JsonRenderer implements Renderer { |
| |
| private final Logger log = LoggerFactory.getLogger(JsonRenderer.class); |
| |
| /** Recursion level selector that means "all levels" */ |
| public static final String INFINITY = "infinity"; |
| |
| /** Selector that means "pretty-print the output */ |
| public static final String TIDY = "tidy"; |
| |
| /** Selector that causes hierarchy to be rendered as arrays |
| * instead of child objects - useful to preserve the order of those |
| * child objects */ |
| public static final String HARRAY = "harray"; |
| |
| /** How much to indent in tidy mode */ |
| public static final int INDENT_SPACES = 2; |
| |
| private long maximumResults; |
| |
| private final JsonToText renderer = new JsonToText(); |
| |
| private boolean ecmaSupport; |
| |
| public JsonRenderer(long maximumResults, boolean ecmaSupport) { |
| this.maximumResults = maximumResults; |
| this.ecmaSupport = ecmaSupport; |
| } |
| |
| public void render(SlingHttpServletRequest req, |
| SlingHttpServletResponse resp) throws IOException { |
| // Access and check our data |
| final Resource r = req.getResource(); |
| if (ResourceUtil.isNonExistingResource(r)) { |
| throw new ResourceNotFoundException("No data to render."); |
| } |
| |
| int maxRecursionLevels = 0; |
| try { |
| maxRecursionLevels = getMaxRecursionLevel(req); |
| } catch(IllegalArgumentException iae) { |
| resp.sendError(HttpServletResponse.SC_BAD_REQUEST, iae.getMessage()); |
| return; |
| } |
| |
| resp.setContentType(req.getResponseContentType()); |
| resp.setCharacterEncoding("UTF-8"); |
| |
| // We check the tree to see if the nr of nodes isn't bigger than the allowed nr. |
| boolean allowDump = true; |
| int allowedLevel = 0; |
| final boolean tidy = isTidy(req); |
| final boolean harray = hasSelector(req, HARRAY); |
| ResourceTraversor traversor = null; |
| try { |
| traversor = new ResourceTraversor(maxRecursionLevels, maximumResults, r, ecmaSupport); |
| allowedLevel = traversor.collectResources(); |
| if ( allowedLevel != -1 ) { |
| allowDump = false; |
| } |
| } catch (final Exception e) { |
| reportException(e); |
| } |
| try { |
| // Dump the resource if we can |
| if (allowDump) { |
| if (tidy || harray) { |
| final JsonToText.Options opt = renderer.options() |
| .withIndent(tidy ? INDENT_SPACES : 0) |
| .withArraysForChildren(harray); |
| resp.getWriter().write(renderer.prettyPrint(traversor.getJSONObject(), opt)); |
| } else { |
| // If no rendering options, use the plain toString() method, for |
| // backwards compatibility. Output might be slightly different |
| // with prettyPrint and no options |
| StringWriter writer = new StringWriter(); |
| try (JsonGenerator json = Json.createGenerator(writer)){ |
| json.write(traversor.getJSONObject()); |
| } |
| resp.getWriter().write(writer.toString()); |
| } |
| |
| } else { |
| // We are not allowed to do the dump. |
| // Send a 300 |
| String tidyUrl = (tidy) ? "tidy." : ""; |
| resp.setStatus(HttpServletResponse.SC_MULTIPLE_CHOICES); |
| StringWriter writer = new StringWriter(); |
| try (JsonGenerator json = Json.createGenerator(writer)) { |
| json.writeStartArray(); |
| while (allowedLevel >= 0) { |
| json.write( |
| r.getResourceMetadata().getResolutionPath() + "." + tidyUrl + allowedLevel + ".json"); |
| allowedLevel--; |
| } |
| json.writeEnd(); |
| } |
| resp.getWriter().write(writer.toString()); |
| } |
| } catch (Exception je) { |
| reportException(je); |
| } |
| } |
| |
| /** |
| * Get recursion level from selectors. as per SLING-167: the last selector, if present, gives the recursion level. |
| * |
| * @param req the request |
| * @return the recursion level |
| * @throws IllegalArgumentException if the detected selector is not a number |
| */ |
| protected int getMaxRecursionLevel(SlingHttpServletRequest req) throws IllegalArgumentException { |
| int maxRecursionLevels = 0; |
| final String[] selectors = req.getRequestPathInfo().getSelectors(); |
| if (selectors != null && selectors.length > 0) { |
| final String level = selectors[selectors.length - 1]; |
| if(!TIDY.equals(level) && !HARRAY.equals(level)) { |
| if (INFINITY.equals(level)) { |
| maxRecursionLevels = -1; |
| } else { |
| try { |
| maxRecursionLevels = Integer.parseInt(level); |
| } catch (NumberFormatException nfe) { |
| //SLING-2324 |
| if (StringUtils.isNumeric(level)){ |
| maxRecursionLevels = -1; |
| } else { |
| throw new IllegalArgumentException("Invalid recursion selector value '" + level + "'"); |
| } |
| } |
| } |
| } |
| } |
| return maxRecursionLevels; |
| } |
| |
| /** |
| * Checks if the provided request contains a certain selector. |
| * @param req the request |
| * @param selectorToCheck the selector |
| * @return {@code true} if the selector is present, {@code false} otherwise |
| */ |
| protected boolean hasSelector(SlingHttpServletRequest req, String selectorToCheck) { |
| for(String selector : req.getRequestPathInfo().getSelectors()) { |
| if(selectorToCheck.equals(selector)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * True if our request wants the "tidy" pretty-printed format |
| * @param req the request |
| * @return {@code true} if the request contains the {@link #TIDY} selector, {@code false} otherwise |
| */ |
| protected boolean isTidy(SlingHttpServletRequest req) { |
| return hasSelector(req, TIDY); |
| } |
| |
| /** |
| * @param e |
| * @throws SlingException wrapping the given exception |
| */ |
| private void reportException(Exception e) { |
| log.warn("Error in JsonRendererServlet: {}", e); |
| throw new SlingException(e.toString(), e); |
| } |
| } |
| |