/* * 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 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.api.servlets.SlingSafeMethodsServlet; import org.apache.sling.servlets.get.impl.util.JsonRenderer; 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 JsonRendererServlet extends SlingSafeMethodsServlet { private final Logger log = LoggerFactory.getLogger(JsonRendererServlet.class); private static final long serialVersionUID = 5577121546674133317L; public static final String EXT_JSON = "json"; /** 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 JsonRenderer renderer = new JsonRenderer(); public JsonRendererServlet(long maximumResults) { this.maximumResults = maximumResults; } @Override protected void doGet(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); 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 JsonRenderer.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 Json.createGenerator(resp.getWriter()).write(traversor.getJSONObject()).close(); } } else { // We are not allowed to do the dump. // Send a 300 String tidyUrl = (tidy) ? "tidy." : ""; resp.setStatus(HttpServletResponse.SC_MULTIPLE_CHOICES); JsonGenerator writer = Json.createGenerator(resp.getWriter()); writer.writeStartArray(); while (allowedLevel >= 0) { writer.write(r.getResourceMetadata().getResolutionPath() + "." + tidyUrl + allowedLevel + ".json"); allowedLevel--; } writer.writeEnd(); writer.close(); } } 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.toString(), e); throw new SlingException(e.toString(), e); } }