/* * Copyright 2017 LINE Corporation * * LINE Corporation 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 com.linecorp.armeria.server.docs; import static com.google.common.base.Preconditions.checkArgument; import static java.util.Objects.requireNonNull; import java.util.HashMap; import java.util.Map; import java.util.Optional; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ListMultimap; import com.linecorp.armeria.common.http.HttpHeaders; /** * Builds a new {@link DocService}. */ public final class DocServiceBuilder { // These maps contain the entries whose key is a service name and value is a multimap. // The multimap contains the entries whose key is the method name. // An empty service name denotes 'all services'. // An empty method name denotes 'all methods'. // When a service name is empty, its method name shouldn't be empty. // For exampleRequests, both service name and method name shouldn't be empty. private final Map<String, ListMultimap<String, HttpHeaders>> exampleHttpHeaders = new HashMap<>(); private final Map<String, ListMultimap<String, String>> exampleRequests = new HashMap<>(); /** * Adds the example {@link HttpHeaders} which are applicable to any services. */ public DocServiceBuilder exampleHttpHeaders(HttpHeaders... exampleHttpHeaders) { requireNonNull(exampleHttpHeaders, "exampleHttpHeaders"); return exampleHttpHeaders(ImmutableList.copyOf(exampleHttpHeaders)); } /** * Adds the example {@link HttpHeaders} which are applicable to any services. */ public DocServiceBuilder exampleHttpHeaders(Iterable<? extends HttpHeaders> exampleHttpHeaders) { return exampleHttpHeaders0("", "", exampleHttpHeaders); } /** * Adds the example {@link HttpHeaders} for the service with the specified type. This method is * a shortcut to: * <pre>{@code * exampleHttpHeaders(serviceType.getName(), exampleHttpHeaders); * }</pre> */ public DocServiceBuilder exampleHttpHeaders(Class<?> serviceType, HttpHeaders... exampleHttpHeaders) { requireNonNull(serviceType, "serviceType"); return exampleHttpHeaders(serviceType.getName(), exampleHttpHeaders); } /** * Adds the example {@link HttpHeaders} for the service with the specified type. This method is * a shortcut to: * <pre>{@code * exampleHttpHeaders(serviceType.getName(), exampleHttpHeaders); * }</pre> */ public DocServiceBuilder exampleHttpHeaders(Class<?> serviceType, Iterable<? extends HttpHeaders> exampleHttpHeaders) { requireNonNull(serviceType, "serviceType"); return exampleHttpHeaders(serviceType.getName(), exampleHttpHeaders); } /** * Adds the example {@link HttpHeaders} for the service with the specified name. */ public DocServiceBuilder exampleHttpHeaders(String serviceName, HttpHeaders... exampleHttpHeaders) { requireNonNull(exampleHttpHeaders, "exampleHttpHeaders"); return exampleHttpHeaders(serviceName, ImmutableList.copyOf(exampleHttpHeaders)); } /** * Adds the example {@link HttpHeaders} for the service with the specified name. */ public DocServiceBuilder exampleHttpHeaders(String serviceName, Iterable<? extends HttpHeaders> exampleHttpHeaders) { requireNonNull(serviceName, "serviceName"); checkArgument(!serviceName.isEmpty(), "serviceName is empty."); requireNonNull(exampleHttpHeaders, "exampleHttpHeaders"); return exampleHttpHeaders0(serviceName, "", exampleHttpHeaders); } /** * Adds the example {@link HttpHeaders} for the method with the specified type and method name. * This method is a shortcut to: * <pre>{@code * exampleHttpHeaders(serviceType.getName(), methodName, exampleHttpHeaders); * }</pre> */ public DocServiceBuilder exampleHttpHeaders(Class<?> serviceType, String methodName, HttpHeaders... exampleHttpHeaders) { requireNonNull(serviceType, "serviceType"); return exampleHttpHeaders(serviceType.getName(), methodName, exampleHttpHeaders); } /** * Adds the example {@link HttpHeaders} for the method with the specified type and method name. * This method is a shortcut to: * <pre>{@code * exampleHttpHeaders(serviceType.getName(), methodName, exampleHttpHeaders); * }</pre> */ public DocServiceBuilder exampleHttpHeaders(Class<?> serviceType, String methodName, Iterable<? extends HttpHeaders> exampleHttpHeaders) { requireNonNull(serviceType, "serviceType"); return exampleHttpHeaders(serviceType.getName(), methodName, exampleHttpHeaders); } /** * Adds the example {@link HttpHeaders} for the method with the specified service and method name. */ public DocServiceBuilder exampleHttpHeaders(String serviceName, String methodName, HttpHeaders... exampleHttpHeaders) { requireNonNull(exampleHttpHeaders, "exampleHttpHeaders"); return exampleHttpHeaders(serviceName, methodName, ImmutableList.copyOf(exampleHttpHeaders)); } /** * Adds the example {@link HttpHeaders} for the method with the specified service and method name. */ public DocServiceBuilder exampleHttpHeaders(String serviceName, String methodName, Iterable<? extends HttpHeaders> exampleHttpHeaders) { requireNonNull(serviceName, "serviceName"); checkArgument(!serviceName.isEmpty(), "serviceName is empty."); requireNonNull(methodName, "methodName"); checkArgument(!methodName.isEmpty(), "methodName is empty."); requireNonNull(exampleHttpHeaders, "exampleHttpHeaders"); return exampleHttpHeaders0(serviceName, methodName, exampleHttpHeaders); } private DocServiceBuilder exampleHttpHeaders0(String serviceName, String methodName, Iterable<? extends HttpHeaders> exampleHttpHeaders) { for (HttpHeaders h : exampleHttpHeaders) { requireNonNull(h, "exampleHttpHeaders contains null."); this.exampleHttpHeaders.computeIfAbsent(serviceName, unused -> ArrayListMultimap.create()) .put(methodName, HttpHeaders.copyOf(h).asImmutable()); } return this; } /** * Adds the example requests for the method with the specified service type and method name. * This method is a shortcut to: * <pre>{@code * exampleRequest(serviceType.getName(), exampleRequests); * }</pre> */ public DocServiceBuilder exampleRequest(Class<?> serviceType, String methodName, Object... exampleRequests) { requireNonNull(exampleRequests, "exampleRequests"); return exampleRequest(serviceType, methodName, ImmutableList.copyOf(exampleRequests)); } /** * Adds the example requests for the method with the specified service type and method name. * This method is a shortcut to: * <pre>{@code * exampleRequest(serviceType.getName(), exampleRequests); * }</pre> */ public DocServiceBuilder exampleRequest(Class<?> serviceType, String methodName, Iterable<?> exampleRequests) { requireNonNull(serviceType, "serviceType"); return exampleRequest(serviceType.getName(), methodName, exampleRequests); } /** * Adds the example requests for the method with the specified service and method name. */ public DocServiceBuilder exampleRequest(String serviceName, String methodName, Object... exampleRequests) { requireNonNull(exampleRequests, "exampleRequests"); return exampleRequest(serviceName, methodName, ImmutableList.copyOf(exampleRequests)); } /** * Adds the example requests for the method with the specified service and method name. */ public DocServiceBuilder exampleRequest(String serviceName, String methodName, Iterable<?> exampleRequests) { requireNonNull(serviceName, "serviceName"); requireNonNull(methodName, "methodName"); requireNonNull(exampleRequests, "exampleRequests"); for (Object e : exampleRequests) { requireNonNull(e, "exampleRequests contains null."); exampleRequest0(serviceName, methodName, serializeExampleRequest(serviceName, methodName, e)); } return this; } /** * Adds the example requests which are applicable to the method denoted by the specified example requests. * Please note that this method may fail if the specified requests object do not provide the information * about their service and method names. * * @throws IllegalArgumentException if failed to get the service and method name from an example request */ public DocServiceBuilder exampleRequest(Object... exampleRequests) { requireNonNull(exampleRequests, "exampleRequests"); return exampleRequest(ImmutableList.copyOf(exampleRequests)); } /** * Adds the example requests which are applicable to the method denoted by the specified example requests. * Please note that this method may fail if the specified requests object do not provide the information * about their service and method names. * * @throws IllegalArgumentException if failed to get the service and method name from an example request */ public DocServiceBuilder exampleRequest(Iterable<?> exampleRequests) { requireNonNull(exampleRequests, "exampleRequests"); for (Object e : exampleRequests) { requireNonNull(e, "exampleRequests contains null."); final String[] result = guessAndSerializeExampleRequest(e); exampleRequest0(result[0], result[1], result[2]); } return this; } private void exampleRequest0(String serviceName, String methodName, String serializedExampleRequest) { exampleRequests.computeIfAbsent(serviceName, unused -> ArrayListMultimap.create()) .put(methodName, serializedExampleRequest); } private static String serializeExampleRequest( String serviceName, String methodName, Object exampleRequest) { if (exampleRequest instanceof CharSequence) { return exampleRequest.toString(); } for (DocServicePlugin generator : DocService.plugins) { final Optional<String> result = generator.serializeExampleRequest(serviceName, methodName, exampleRequest); if (result.isPresent()) { return result.get(); } } throw new IllegalArgumentException("could not find a plugin that can serialize: " + exampleRequest); } /** * Returns a tuple of a service name, a method name and a serialized example request. */ private static String[] guessAndSerializeExampleRequest(Object exampleRequest) { checkArgument(!(exampleRequest instanceof CharSequence), "can't guess service or method name from a string: ", exampleRequest); boolean guessed = false; for (DocServicePlugin plugin : DocService.plugins) { // Skip if the plugin does not support it. if (plugin.supportedExampleRequestTypes().stream() .noneMatch(type -> type.isInstance(exampleRequest))) { continue; } final Optional<String> serviceName = plugin.guessServiceName(exampleRequest); final Optional<String> methodName = plugin.guessServiceMethodName(exampleRequest); // Skip if the plugin cannot guess the service and method name. if (!serviceName.isPresent() || !methodName.isPresent()) { continue; } guessed = true; final String s = serviceName.get(); final String f = methodName.get(); Optional<String> serialized = plugin.serializeExampleRequest(s, f, exampleRequest); if (serialized.isPresent()) { return new String[] { s, f, serialized.get() }; } } if (guessed) { throw new IllegalArgumentException( "could not find a plugin that can serialize: " + exampleRequest); } else { throw new IllegalArgumentException( "could not find a plugin that can guess the service and method name from: " + exampleRequest); } } /** * Creates a new {@link DocService}. */ public DocService build() { return new DocService(exampleHttpHeaders, exampleRequests); } }