001/**
002 * Copyright (C) 2006-2024 Talend Inc. - www.talend.com
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.talend.sdk.component.runtime.manager.service.http.codec;
017
018import static java.util.Locale.ROOT;
019
020import java.util.Map;
021import java.util.Optional;
022import java.util.concurrent.ConcurrentHashMap;
023import java.util.concurrent.ConcurrentMap;
024
025import org.talend.sdk.component.api.service.http.ContentType;
026import org.talend.sdk.component.api.service.http.Decoder;
027import org.talend.sdk.component.api.service.http.Encoder;
028
029/**
030 * Codec matcher using content type defined with {@link ContentType} on {@link Decoder}, {@link Encoder} implementations
031 *
032 * @param <T> the type of object selected.
033 */
034public class CodecMatcher<T> {
035
036    private final ConcurrentMap<String, T> cache = new ConcurrentHashMap<>();
037
038    /**
039     * select the suitable codec for the content type
040     *
041     * @param codecList map of codec
042     * @param contentType content type to be handled
043     * @return the suitable codec for the content type
044     */
045    public T select(final Map<String, T> codecList, final String contentType) {
046        final String mediaType = extractMediaType(contentType);
047        return cache.computeIfAbsent(mediaType, k -> {
048            if (codecList.containsKey(mediaType)) { // exact match
049                return codecList.get(mediaType);
050            }
051
052            // regex match
053            final Optional<T> matched = codecList
054                    .entrySet()
055                    .stream()
056                    .filter(e -> mediaType.matches(e.getKey().replace("+", "\\+").replace("*", ".+")))
057                    .findFirst()
058                    .map(Map.Entry::getValue);
059            return matched
060                    .orElseThrow(
061                            () -> new IllegalStateException("No codec found for content-type: '" + contentType + "'"));
062
063        });
064    }
065
066    private String extractMediaType(final String contentType) {
067        if (contentType == null || contentType.isEmpty()) {
068            return "*/*";
069        }
070        // content-type contains charset and/or boundary
071        return ((contentType.contains(";")) ? contentType.split(";")[0] : contentType).toLowerCase(ROOT);
072
073    }
074}