<commit_before>/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2019, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.wildfly.extension.microprofile.openapi.deployment;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.ws.rs.core.MediaType;

import org.eclipse.microprofile.openapi.models.OpenAPI;
import org.jboss.resteasy.util.AcceptParser;

import io.smallrye.openapi.runtime.io.OpenApiSerializer;
import io.smallrye.openapi.runtime.io.OpenApiSerializer.Format;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.HeaderMap;
import io.undertow.util.Headers;
import io.undertow.util.HttpString;
import io.undertow.util.Methods;
import io.undertow.util.StatusCodes;

/**
 * {@link HttpHandler} for the Open API endpoint.
 * @author <NAME>
 * @author <NAME>
 */
public class OpenAPIHttpHandler implements HttpHandler {

    private static final String ALLOW_METHODS = String.join(",", Methods.GET_STRING, Methods.HEAD_STRING, Methods.OPTIONS_STRING);
    private static final String DEFAULT_ALLOW_HEADERS = String.join(",", Headers.CONTENT_TYPE_STRING, Headers.AUTHORIZATION_STRING);
    private static final long DEFAULT_MAX_AGE = ChronoUnit.DAYS.getDuration().getSeconds();
    private static final Map<MediaType, Format> ACCEPTED_TYPES = new LinkedHashMap<>();
    private static final Map<String, Format> FORMATS = new HashMap<>();
    private static final String FORMAT = "format";

    static {
        for (Format format : EnumSet.allOf(Format.class)) {
            ACCEPTED_TYPES.put(MediaType.valueOf(format.getMimeType()), format);
            FORMATS.put(format.name(), format);
        }
    }

    private final OpenAPI model;

    public OpenAPIHttpHandler(OpenAPI model) {
        this.model = model;
    }

    @Override
    public void handleRequest(HttpServerExchange exchange) throws IOException {
        HttpString requestMethod = exchange.getRequestMethod();
        HeaderMap responseHeaders = exchange.getResponseHeaders();

        // Add CORS response headers
        responseHeaders.put(new HttpString("Access-Control-Allow-Origin"), "*");
        responseHeaders.put(new HttpString("Access-Control-Allow-Credentials"), "true");
        responseHeaders.put(new HttpString("Access-Control-Allow-Methods"), ALLOW_METHODS);
        responseHeaders.put(new HttpString("Access-Control-Allow-Headers"), DEFAULT_ALLOW_HEADERS);
        responseHeaders.put(new HttpString("Access-Control-Max-Age"), DEFAULT_MAX_AGE);

        if (requestMethod.equals(Methods.GET) || requestMethod.equals(Methods.HEAD)) {
            // Determine preferred media type
            List<MediaType> preferredTypes = Collections.emptyList();
            List<MediaType> types = parseAcceptedTypes(exchange);

            for (MediaType type : types) {
                List<MediaType> compatibleTypes = new ArrayList<>(ACCEPTED_TYPES.size());
                for (MediaType acceptedType : ACCEPTED_TYPES.keySet()) {
                    if (type.isCompatible(acceptedType)) {
                        compatibleTypes.add(acceptedType);
                    }
                }
                if (!compatibleTypes.isEmpty()) {
                    preferredTypes = compatibleTypes;
                    break;
                }
            }

            // Determine preferred charset
            Charset charset = parseCharset(exchange);

            if (preferredTypes.isEmpty() || (charset == null)) {
                exchange.setStatusCode(StatusCodes.NOT_ACCEPTABLE);
                return;
            }

            // Use format preferred by Accept header if unambiguous, otherwise determine format from query parameter
            Format format = (preferredTypes.size() == 1) ? ACCEPTED_TYPES.get(preferredTypes.get(0)) : parseFormatParameter(exchange);

            String result = OpenApiSerializer.serialize(this.model, format);

            responseHeaders.put(Headers.CONTENT_TYPE, format.getMimeType());
            responseHeaders.put(Headers.CONTENT_LENGTH, result.length());

            if (requestMethod.equals(Methods.GET)) {
                exchange.getResponseSender().send(result, charset);
            }
        } else if (requestMethod.equals(Methods.OPTIONS)) {
            responseHeaders.put(Headers.ALLOW, ALLOW_METHODS);
        } else {
            exchange.setStatusCode(StatusCodes.METHOD_NOT_ALLOWED);
        }
    }

    private static final Comparator<MediaType> MEDIA_TYPE_SORTER = new Comparator<MediaType>() {
        @Override<commit_msg>WFLY-13333 Content-Length should specify length of the *encoded* document.<commit_after>/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2019, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.wildfly.extension.microprofile.openapi.deployment;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.ws.rs.core.MediaType;

import org.eclipse.microprofile.openapi.models.OpenAPI;
import org.jboss.resteasy.util.AcceptParser;

import io.smallrye.openapi.runtime.io.OpenApiSerializer;
import io.smallrye.openapi.runtime.io.OpenApiSerializer.Format;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.HeaderMap;
import io.undertow.util.Headers;
import io.undertow.util.HttpString;
import io.undertow.util.Methods;
import io.undertow.util.StatusCodes;

/**
 * {@link HttpHandler} for the Open API endpoint.
 * @author <NAME>
 * @author <NAME>
 */
public class OpenAPIHttpHandler implements HttpHandler {

    private static final String ALLOW_METHODS = String.join(",", Methods.GET_STRING, Methods.HEAD_STRING, Methods.OPTIONS_STRING);
    private static final String DEFAULT_ALLOW_HEADERS = String.join(",", Headers.CONTENT_TYPE_STRING, Headers.AUTHORIZATION_STRING);
    private static final long DEFAULT_MAX_AGE = ChronoUnit.DAYS.getDuration().getSeconds();
    private static final Map<MediaType, Format> ACCEPTED_TYPES = new LinkedHashMap<>();
    private static final Map<String, Format> FORMATS = new HashMap<>();
    private static final String FORMAT = "format";

    static {
        for (Format format : EnumSet.allOf(Format.class)) {
            ACCEPTED_TYPES.put(MediaType.valueOf(format.getMimeType()), format);
            FORMATS.put(format.name(), format);
        }
    }

    private final OpenAPI model;

    public OpenAPIHttpHandler(OpenAPI model) {
        this.model = model;
    }

    @Override
    public void handleRequest(HttpServerExchange exchange) throws IOException {
        HttpString requestMethod = exchange.getRequestMethod();
        HeaderMap responseHeaders = exchange.getResponseHeaders();

        // Add CORS response headers
        responseHeaders.put(new HttpString("Access-Control-Allow-Origin"), "*");
        responseHeaders.put(new HttpString("Access-Control-Allow-Credentials"), "true");
        responseHeaders.put(new HttpString("Access-Control-Allow-Methods"), ALLOW_METHODS);
        responseHeaders.put(new HttpString("Access-Control-Allow-Headers"), DEFAULT_ALLOW_HEADERS);
        responseHeaders.put(new HttpString("Access-Control-Max-Age"), DEFAULT_MAX_AGE);

        if (requestMethod.equals(Methods.GET) || requestMethod.equals(Methods.HEAD)) {
            // Determine preferred media type
            List<MediaType> preferredTypes = Collections.emptyList();
            List<MediaType> types = parseAcceptedTypes(exchange);

            for (MediaType type : types) {
                List<MediaType> compatibleTypes = new ArrayList<>(ACCEPTED_TYPES.size());
                for (MediaType acceptedType : ACCEPTED_TYPES.keySet()) {
                    if (type.isCompatible(acceptedType)) {
                        compatibleTypes.add(acceptedType);
                    }
                }
                if (!compatibleTypes.isEmpty()) {
                    preferredTypes = compatibleTypes;
                    break;
                }
            }

            // Determine preferred charset
            Charset charset = parseCharset(exchange);

            if (preferredTypes.isEmpty() || (charset == null)) {
                exchange.setStatusCode(StatusCodes.NOT_ACCEPTABLE);
                return;
            }

            // Use format preferred by Accept header if unambiguous, otherwise determine format from query parameter
            Format format = (preferredTypes.size() == 1) ? ACCEPTED_TYPES.get(preferredTypes.get(0)) : parseFormatParameter(exchange);

            byte[] result = OpenApiSerializer.serialize(this.model, format).getBytes(charset);

            responseHeaders.put(Headers.CONTENT_TYPE, format.getMimeType());
            responseHeaders.put(Headers.CONTENT_LENGTH, result.length);

            if (requestMethod.equals(Methods.GET)) {
                exchange.getResponseSender().send(ByteBuffer.wrap(result));
            }
        } else if (requestMethod.equals(Methods.OPTIONS)) {
            responseHeaders.put(Headers.ALLOW, ALLOW_METHODS);
        } else {
            exchange.setStatusCode(StatusCodes.METHOD_NOT_ALLOWED);
        }
    }

    private static final Comparator<MediaType> MEDIA_TYPE_SORTER = new Comparator<MediaType>() {
        @Override
{'fraction_non_alphanumeric': 0.09639478394272565, 'fraction_numerical': 0.0036648768430921335, 'mean_word_length': 3.2223821518531848, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2776421', 'n_tokens_mistral': 3201, 'n_tokens_neox': 3083, 'n_words': 828}