blob: 9a53d11a9efebc79e659b3728da4d8f143a00bec [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.qpid.server.management.plugin.servlet.rest;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.google.common.base.Strings;
import org.apache.qpid.server.management.plugin.HttpManagementUtil;
import org.apache.qpid.server.management.plugin.ManagementController;
import org.apache.qpid.server.management.plugin.ManagementException;
import org.apache.qpid.server.management.plugin.ManagementRequest;
import org.apache.qpid.server.management.plugin.ManagementResponse;
import org.apache.qpid.server.management.plugin.ResponseType;
import org.apache.qpid.server.model.ConfiguredObject;
import org.apache.qpid.server.model.ConfiguredObjectJacksonModule;
import org.apache.qpid.server.model.Content;
import org.apache.qpid.server.model.port.HttpPort;
import org.apache.qpid.server.util.DataUrlUtils;
public class RestServlet extends AbstractServlet
{
private static final long serialVersionUID = 1L;
private static final String APPLICATION_JSON = "application/json";
private transient ManagementController _managementController;
@SuppressWarnings("unused")
public RestServlet()
{
super();
}
@Override
public void init() throws ServletException
{
super.init();
final ServletConfig servletConfig = getServletConfig();
final ServletContext servletContext = servletConfig.getServletContext();
final String modelVersion = servletConfig.getInitParameter("qpid.controller.version");
if (modelVersion == null)
{
throw new ServletException("Controller version is not specified");
}
@SuppressWarnings("uncjecked")
ManagementController controller = (ManagementController) servletContext.getAttribute("qpid.controller.chain");
do
{
if (controller.getVersion().equals(modelVersion))
{
_managementController = controller;
break;
}
controller = controller.getNextVersionManagementController();
}
while (controller != null);
if (_managementController == null)
{
throw new ServletException("Controller is not found");
}
}
@Override
protected void doGet(final HttpServletRequest httpServletRequest,
final HttpServletResponse httpServletResponse,
final ConfiguredObject<?> managedObject)
throws IOException
{
try
{
final ManagementRequest request = new ServletManagementRequest(managedObject, httpServletRequest);
final ManagementController controller = getManagementController();
final ManagementResponse response = controller.handleGet(request);
sendResponse(request, response, httpServletRequest, httpServletResponse, controller);
}
catch (ManagementException e)
{
sendResponse(e, httpServletRequest, httpServletResponse);
}
}
@Override
protected void doPost(final HttpServletRequest httpServletRequest,
final HttpServletResponse httpServletResponse,
final ConfiguredObject<?> managedObject) throws IOException
{
try
{
final ManagementRequest request = new ServletManagementRequest(managedObject, httpServletRequest);
final ManagementController controller = getManagementController();
final ManagementResponse response = controller.handlePost(request);
sendResponse(request, response, httpServletRequest, httpServletResponse, controller);
}
catch (ManagementException e)
{
sendResponse(e, httpServletRequest, httpServletResponse);
}
}
@Override
protected void doPut(final HttpServletRequest httpServletRequest,
final HttpServletResponse httpServletResponse,
final ConfiguredObject<?> managedObject) throws IOException
{
try
{
final ManagementRequest request = new ServletManagementRequest(managedObject, httpServletRequest);
final ManagementController controller = getManagementController();
final ManagementResponse response = controller.handlePut(request);
sendResponse(request, response, httpServletRequest, httpServletResponse, controller);
}
catch (ManagementException e)
{
sendResponse(e, httpServletRequest, httpServletResponse);
}
}
@Override
protected void doDelete(final HttpServletRequest httpServletRequest,
final HttpServletResponse httpServletResponse,
final ConfiguredObject<?> managedObject) throws IOException
{
try
{
final ManagementRequest request = new ServletManagementRequest(managedObject, httpServletRequest);
final ManagementController controller = getManagementController();
final ManagementResponse response = controller.handleDelete(request);
sendResponse(request, response, httpServletRequest, httpServletResponse, controller);
}
catch (ManagementException e)
{
sendResponse(e, httpServletRequest, httpServletResponse);
}
}
private ManagementController getManagementController()
{
return _managementController;
}
private void sendResponse(final ManagementException managementException,
final HttpServletRequest request,
final HttpServletResponse response) throws IOException
{
setHeaders(response);
setExceptionHeaders(managementException, response);
response.setStatus(managementException.getStatusCode());
writeJsonResponse(Collections.singletonMap("errorMessage", managementException.getMessage()),
request,
response);
}
private void setExceptionHeaders(final ManagementException managementException, final HttpServletResponse response)
{
Map<String, String> headers = managementException.getHeaders();
if (headers != null)
{
headers.forEach(response::setHeader);
}
}
private String toContentDispositionHeader(final String attachmentFilename)
{
String filenameRfc2183 = HttpManagementUtil.ensureFilenameIsRfc2183(attachmentFilename);
if (filenameRfc2183.length() > 0)
{
return String.format("attachment; filename=\"%s\"", filenameRfc2183);
}
else
{
// Agent will allow user to choose a name
return "attachment";
}
}
private void sendResponse(final ManagementRequest managementRequest,
final ManagementResponse managementResponse,
final HttpServletRequest request,
final HttpServletResponse response,
final ManagementController controller) throws IOException
{
setHeaders(response);
Map<String, String> headers = managementResponse.getHeaders();
if (!headers.isEmpty())
{
headers.forEach(response::setHeader);
}
Map<String, List<String>> parameters = managementRequest.getParameters();
if (parameters.containsKey(CONTENT_DISPOSITION_ATTACHMENT_FILENAME_PARAM))
{
String attachmentFilename = managementRequest.getParameter(CONTENT_DISPOSITION_ATTACHMENT_FILENAME_PARAM);
response.setHeader(CONTENT_DISPOSITION, toContentDispositionHeader(attachmentFilename));
}
response.setStatus(managementResponse.getResponseCode());
Object body = managementResponse.getBody();
if (body instanceof Content)
{
Content content = (Content) body;
try
{
writeTypedContent(content, request, response);
}
finally
{
content.release();
}
}
else if (body != null)
{
response.setContentType(APPLICATION_JSON);
Object data;
if (managementResponse.getType() == ResponseType.MODEL_OBJECT)
{
data = controller.formatConfiguredObject(
managementResponse.getBody(),
parameters,
managementRequest.isSecure()
|| managementRequest.isConfidentialOperationAllowedOnInsecureChannel());
}
else
{
data = managementResponse.getBody();
}
writeJsonResponse(data, request, response);
}
}
private void setHeaders(final HttpServletResponse response)
{
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
}
private void writeJsonResponse(final Object formattedResponse,
final HttpServletRequest request,
final HttpServletResponse response) throws IOException
{
try (OutputStream stream = HttpManagementUtil.getOutputStream(request,
response,
getManagementConfiguration()))
{
ObjectMapper mapper = ConfiguredObjectJacksonModule.newObjectMapper(false);
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
mapper.writeValue(stream, formattedResponse);
}
}
private static Map<String, List<String>> parseQueryString(String queryString)
{
if (Strings.isNullOrEmpty(queryString))
{
return Collections.emptyMap();
}
Map<String, List<String>> query = new LinkedHashMap<>();
final String[] pairs = queryString.split("&");
for (String pairString : pairs)
{
List<String> pair = new ArrayList<>(Arrays.asList(pairString.split("=")));
if (pair.size() == 1)
{
pair.add(null);
}
else if (pair.size() != 2)
{
throw new IllegalArgumentException(String.format("could not parse query string '%s'", queryString));
}
String key;
String value;
try
{
key = URLDecoder.decode(pair.get(0), "UTF-8");
value = pair.get(1) == null ? null : URLDecoder.decode(pair.get(1), "UTF-8");
}
catch (UnsupportedEncodingException e)
{
throw new RuntimeException(e);
}
if (!query.containsKey(key))
{
query.put(key, new ArrayList<>());
}
query.get(key).add(value);
}
return query;
}
private static class ServletManagementRequest implements ManagementRequest
{
private final HttpPort<?> _port;
private final HttpServletRequest _request;
private final Map<String, List<String>> _query;
private final List<String> _path;
private final ConfiguredObject<?> _root;
private final String _category;
private final Map<String, String> _headers;
ServletManagementRequest(final ConfiguredObject<?> root,
final HttpServletRequest request)
{
_root = root;
_request = request;
_port = HttpManagementUtil.getPort(request);
_query = Collections.unmodifiableMap(parseQueryString(request.getQueryString()));
String pathInfo = _request.getPathInfo() == null ? "" : _request.getPathInfo();
String servletPath = request.getServletPath();
_path = Collections.unmodifiableList(HttpManagementUtil.getPathInfoElements(servletPath, pathInfo));
final String[] servletPathElements = servletPath.split("/");
_category = servletPathElements[servletPathElements.length - 1];
final Map<String, String> headers = Collections.list(request.getHeaderNames())
.stream()
.collect(Collectors.toMap(name -> name, request::getHeader));
_headers = Collections.unmodifiableMap(headers);
}
public ConfiguredObject<?> getRoot()
{
return _root;
}
public boolean isSecure()
{
return _request.isSecure();
}
public boolean isConfidentialOperationAllowedOnInsecureChannel()
{
return _port.isAllowConfidentialOperationsOnInsecureChannels();
}
public List<String> getPath()
{
return _path;
}
public String getMethod()
{
return _request.getMethod();
}
public Map<String, List<String>> getParameters()
{
return Collections.unmodifiableMap(_query);
}
@Override
public String getParameter(final String name)
{
final List<String> values = _query.get(name);
return values == null || values.isEmpty() ? null : values.get(0);
}
public Map<String, String> getHeaders()
{
return _headers;
}
@SuppressWarnings("unchecked")
public <T> T getBody(Class<T> type)
{
try
{
return parse(type);
}
catch (IOException | ServletException e)
{
throw ManagementException.createBadRequestManagementException("Cannot parse body", e);
}
}
@Override
public String getRequestURL()
{
return _request.getRequestURL().toString();
}
@SuppressWarnings("unchecked")
private <T> T parse(Class<T> type) throws IOException, ServletException
{
T providedObject;
final ObjectMapper mapper = new ObjectMapper();
if (_headers.containsKey("Content-Type") && _request.getHeader("Content-Type")
.startsWith("multipart/form-data"))
{
Map<String, Object> items = new LinkedHashMap<>();
Map<String, String> fileUploads = new HashMap<>();
Collection<Part> parts = _request.getParts();
for (Part part : parts)
{
if ("data".equals(part.getName()) && "application/json".equals(part.getContentType()))
{
items = mapper.readValue(part.getInputStream(), LinkedHashMap.class);
}
else
{
byte[] data = new byte[(int) part.getSize()];
try (InputStream inputStream = part.getInputStream())
{
inputStream.read(data);
}
fileUploads.put(part.getName(), DataUrlUtils.getDataUrlForBytes(data));
}
}
items.putAll(fileUploads);
providedObject = (T) items;
}
else
{
providedObject = mapper.readValue(_request.getInputStream(), type);
}
return providedObject;
}
@Override
public Map<String, Object> getParametersAsFlatMap()
{
final Map<String, Object> providedObject = new HashMap<>();
for (Map.Entry<String, List<String>> entry : _query.entrySet())
{
final List<String> value = entry.getValue();
if (value != null)
{
if (value.size() == 1)
{
providedObject.put(entry.getKey(), value.get(0));
}
else
{
providedObject.put(entry.getKey(), value);
}
}
}
return providedObject;
}
@Override
public String getCategory()
{
return _category;
}
}
}