Java tutorial
/* * Copyright 2014-2018 the original author or authors. * * Licensed 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 * * https://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.springframework.restdocs.generate; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.BiFunction; import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.operation.OperationRequest; import org.springframework.restdocs.operation.OperationResponse; import org.springframework.restdocs.operation.RequestConverter; import org.springframework.restdocs.operation.ResponseConverter; import org.springframework.restdocs.operation.StandardOperation; import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor; import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor; import org.springframework.restdocs.snippet.Snippet; import org.springframework.util.Assert; /** * A {@code RestDocumentationGenerator} is used to generate documentation snippets from * the request and response of an operation performed on a service. * * @param <REQ> the request type that can be handled * @param <RESP> the response type that can be handled * @author Andy Wilkinson * @author Filip Hrisafov * @since 1.1.0 */ public final class RestDocumentationGenerator<REQ, RESP> { /** * Name of the operation attribute used to hold the request's URL template. */ public static final String ATTRIBUTE_NAME_URL_TEMPLATE = "org.springframework.restdocs.urlTemplate"; /** * Name of the operation attribute used to hold the {@link List} of default snippets. */ public static final String ATTRIBUTE_NAME_DEFAULT_SNIPPETS = "org.springframework.restdocs.defaultSnippets"; /** * Name of the operation attribute used to hold the default operation request * preprocessor. */ public static final String ATTRIBUTE_NAME_DEFAULT_OPERATION_REQUEST_PREPROCESSOR = "org.springframework.restdocs.defaultOperationRequestPreprocessor"; /** * Name of the operation attribute used to hold the default operation response * preprocessor. */ public static final String ATTRIBUTE_NAME_DEFAULT_OPERATION_RESPONSE_PREPROCESSOR = "org.springframework.restdocs.defaultOperationResponsePreprocessor"; private final String identifier; private final OperationRequestPreprocessor requestPreprocessor; private final OperationResponsePreprocessor responsePreprocessor; private final List<Snippet> snippets; private final RequestConverter<REQ> requestConverter; private final ResponseConverter<RESP> responseConverter; /** * Creates a new {@code RestDocumentationGenerator} for the operation identified by * the given {@code identifier}. The given {@code requestConverter} and * {@code responseConverter} are used to convert the operation's request and response * into generic {@code OperationRequest} and {@code OperationResponse} instances that * can then be documented. The given documentation {@code snippets} will be produced. * @param identifier the identifier for the operation * @param requestConverter the request converter * @param responseConverter the response converter * @param snippets the snippets */ public RestDocumentationGenerator(String identifier, RequestConverter<REQ> requestConverter, ResponseConverter<RESP> responseConverter, Snippet... snippets) { this(identifier, requestConverter, responseConverter, new IdentityOperationRequestPreprocessor(), new IdentityOperationResponsePreprocessor(), snippets); } /** * Creates a new {@code RestDocumentationGenerator} for the operation identified by * the given {@code identifier}. The given {@code requestConverter} and * {@code responseConverter} are used to convert the operation's request and response * into generic {@code OperationRequest} and {@code OperationResponse} instances that * can then be documented. The given {@code requestPreprocessor} is applied to the * request before it is documented. The given documentation {@code snippets} will be * produced. * @param identifier the identifier for the operation * @param requestConverter the request converter * @param responseConverter the response converter * @param requestPreprocessor the request preprocessor * @param snippets the snippets */ public RestDocumentationGenerator(String identifier, RequestConverter<REQ> requestConverter, ResponseConverter<RESP> responseConverter, OperationRequestPreprocessor requestPreprocessor, Snippet... snippets) { this(identifier, requestConverter, responseConverter, requestPreprocessor, new IdentityOperationResponsePreprocessor(), snippets); } /** * Creates a new {@code RestDocumentationGenerator} for the operation identified by * the given {@code identifier}. The given {@code requestConverter} and * {@code responseConverter} are used to convert the operation's request and response * into generic {@code OperationRequest} and {@code OperationResponse} instances that * can then be documented. The given {@code responsePreprocessor} is applied to the * response before it is documented. The given documentation {@code snippets} will be * produced. * @param identifier the identifier for the operation * @param requestConverter the request converter * @param responseConverter the response converter * @param responsePreprocessor the response preprocessor * @param snippets the snippets */ public RestDocumentationGenerator(String identifier, RequestConverter<REQ> requestConverter, ResponseConverter<RESP> responseConverter, OperationResponsePreprocessor responsePreprocessor, Snippet... snippets) { this(identifier, requestConverter, responseConverter, new IdentityOperationRequestPreprocessor(), responsePreprocessor, snippets); } /** * Creates a new {@code RestDocumentationGenerator} for the operation identified by * the given {@code identifier}. The given {@code requestConverter} and * {@code responseConverter} are used to convert the operation's request and response * into generic {@code OperationRequest} and {@code OperationResponse} instances that * can then be documented. The given {@code requestPreprocessor} and * {@code responsePreprocessor} are applied to the request and response before they * are documented. The given documentation {@code snippets} will be produced. * @param identifier the identifier for the operation * @param requestConverter the request converter * @param responseConverter the response converter * @param requestPreprocessor the request preprocessor * @param responsePreprocessor the response preprocessor * @param snippets the snippets */ public RestDocumentationGenerator(String identifier, RequestConverter<REQ> requestConverter, ResponseConverter<RESP> responseConverter, OperationRequestPreprocessor requestPreprocessor, OperationResponsePreprocessor responsePreprocessor, Snippet... snippets) { Assert.notNull(identifier, "identifier must be non-null"); Assert.notNull(requestConverter, "requestConverter must be non-null"); Assert.notNull(responseConverter, "responseConverter must be non-null"); Assert.notNull(identifier, "identifier must be non-null"); Assert.notNull(requestPreprocessor, "requestPreprocessor must be non-null"); Assert.notNull(responsePreprocessor, "responsePreprocessor must be non-null"); Assert.notNull(snippets, "snippets must be non-null"); this.identifier = identifier; this.requestConverter = requestConverter; this.responseConverter = responseConverter; this.requestPreprocessor = requestPreprocessor; this.responsePreprocessor = responsePreprocessor; this.snippets = new ArrayList<>(Arrays.asList(snippets)); } /** * Handles the given {@code request} and {@code response}, producing documentation * snippets for them using the given {@code configuration}. * @param request the request * @param response the request * @param configuration the configuration * @throws RestDocumentationGenerationException if a failure occurs during handling */ public void handle(REQ request, RESP response, Map<String, Object> configuration) { Map<String, Object> attributes = new HashMap<>(configuration); OperationRequest operationRequest = preprocessRequest(this.requestConverter.convert(request), attributes); OperationResponse operationResponse = preprocessResponse(this.responseConverter.convert(response), attributes); Operation operation = new StandardOperation(this.identifier, operationRequest, operationResponse, attributes); try { for (Snippet snippet : getSnippets(attributes)) { snippet.document(operation); } } catch (IOException ex) { throw new RestDocumentationGenerationException(ex); } } /** * Creates a new {@link RestDocumentationGenerator} with the same configuration as * this one other than its snippets. The new generator will use the given * {@code snippets}. * @param snippets the snippets * @return the new generator */ public RestDocumentationGenerator<REQ, RESP> withSnippets(Snippet... snippets) { return new RestDocumentationGenerator<>(this.identifier, this.requestConverter, this.responseConverter, this.requestPreprocessor, this.responsePreprocessor, snippets); } @SuppressWarnings("unchecked") private List<Snippet> getSnippets(Map<String, Object> configuration) { List<Snippet> combinedSnippets = new ArrayList<>(); List<Snippet> defaultSnippets = (List<Snippet>) configuration.get(ATTRIBUTE_NAME_DEFAULT_SNIPPETS); if (defaultSnippets != null) { combinedSnippets.addAll(defaultSnippets); } combinedSnippets.addAll(this.snippets); return combinedSnippets; } private OperationRequest preprocessRequest(OperationRequest request, Map<String, Object> configuration) { return preprocess(getRequestPreprocessors(configuration), request, this::preprocess); } private OperationRequest preprocess(OperationRequestPreprocessor preprocessor, OperationRequest request) { return preprocessor.preprocess(request); } private List<OperationRequestPreprocessor> getRequestPreprocessors(Map<String, Object> configuration) { return getPreprocessors(this.requestPreprocessor, RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_OPERATION_REQUEST_PREPROCESSOR, configuration); } private OperationResponse preprocessResponse(OperationResponse response, Map<String, Object> configuration) { return preprocess(getResponsePreprocessors(configuration), response, this::preprocess); } private OperationResponse preprocess(OperationResponsePreprocessor preprocessor, OperationResponse response) { return preprocessor.preprocess(response); } private List<OperationResponsePreprocessor> getResponsePreprocessors(Map<String, Object> configuration) { return getPreprocessors(this.responsePreprocessor, RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_OPERATION_RESPONSE_PREPROCESSOR, configuration); } private <P, T> T preprocess(List<P> preprocessors, T target, BiFunction<P, T, T> function) { T processed = target; for (P preprocessor : preprocessors) { processed = function.apply(preprocessor, processed); } return processed; } @SuppressWarnings("unchecked") private <T> List<T> getPreprocessors(T preprocessor, String preprocessorAttribute, Map<String, Object> configuration) { List<T> preprocessors = new ArrayList<>(2); preprocessors.add(preprocessor); T defaultResponsePreprocessor = (T) configuration.get(preprocessorAttribute); if (defaultResponsePreprocessor != null) { preprocessors.add(defaultResponsePreprocessor); } return preprocessors; } private static final class IdentityOperationRequestPreprocessor implements OperationRequestPreprocessor { @Override public OperationRequest preprocess(OperationRequest request) { return request; } } private static final class IdentityOperationResponsePreprocessor implements OperationResponsePreprocessor { @Override public OperationResponse preprocess(OperationResponse response) { return response; } } }