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.chain.internal;
017
018import static lombok.AccessLevel.PRIVATE;
019import static org.talend.sdk.component.runtime.manager.util.Overrides.override;
020
021import java.net.URI;
022import java.nio.charset.StandardCharsets;
023import java.util.HashMap;
024import java.util.Map;
025
026import lombok.Data;
027import lombok.NoArgsConstructor;
028
029@NoArgsConstructor(access = PRIVATE)
030public class DSLParser {
031
032    public static Step parse(final String rawUri) {
033        final URI uri = URI.create(rawUri);
034        final Map<String, String> query = parseQuery(uri.getRawQuery());
035        final String version = query.remove("__version");
036        return new Step(uri.getScheme(), uri.getAuthority(), version != null ? Integer.parseInt(version.trim()) : -1,
037                query);
038    }
039
040    // taken from tomcat and adapted to this need
041    private static Map<String, String> parseQuery(final String query) {
042        final Map<String, String> parameters = new HashMap<>();
043        if (query == null || query.trim().isEmpty()) {
044            return parameters;
045        }
046
047        final byte[] bytes = query.getBytes(StandardCharsets.UTF_8);
048
049        int pos = 0;
050        int end = bytes.length;
051
052        String name;
053        String value;
054        while (pos < end) {
055            int nameStart = pos;
056            int nameEnd = -1;
057            int valueStart = -1;
058            int valueEnd = -1;
059
060            boolean parsingName = true;
061            boolean decodeName = false;
062            boolean decodeValue = false;
063            boolean parameterComplete = false;
064
065            do {
066                switch (bytes[pos]) {
067                case '=':
068                    if (parsingName) {
069                        nameEnd = pos;
070                        parsingName = false;
071                        valueStart = ++pos;
072                    } else {
073                        pos++;
074                    }
075                    break;
076                case '&':
077                    if (parsingName) {
078                        // Name finished. No value.
079                        nameEnd = pos;
080                    } else {
081                        // Value finished
082                        valueEnd = pos;
083                    }
084                    parameterComplete = true;
085                    pos++;
086                    break;
087                case '%':
088                case '+':
089                    // Decoding required
090                    if (parsingName) {
091                        decodeName = true;
092                    } else {
093                        decodeValue = true;
094                    }
095                    pos++;
096                    break;
097                default:
098                    pos++;
099                    break;
100                }
101            } while (!parameterComplete && pos < end);
102
103            if (pos == end) {
104                if (nameEnd == -1) {
105                    nameEnd = pos;
106                } else if (valueStart > -1 && valueEnd == -1) {
107                    valueEnd = pos;
108                }
109            }
110
111            if (nameEnd <= nameStart) {
112                continue;
113            }
114
115            name = new String(bytes, nameStart, nameEnd - nameStart);
116            if (decodeName) {
117                name = decode(name);
118            }
119            if (valueStart >= 0) {
120                value = new String(bytes, valueStart, valueEnd - valueStart);
121                if (decodeValue) {
122                    value = decode(value);
123                }
124            } else {
125                value = "true"; // tomcat defaults to "", but for us (configuration) true is likely better
126            }
127
128            parameters.put(name, value);
129        }
130        return parameters;
131    }
132
133    private static String decode(final String query) {
134        // cheap impl
135        return URI.create("foo://bar?" + query).getQuery();
136    }
137
138    @Data
139    public static class Step {
140
141        private final String family;
142
143        private final String component;
144
145        private final int version;
146
147        private final Map<String, String> configuration;
148
149        public Step withOverride(final boolean override) {
150            if (override) {
151                return new Step(family, component, version, override(family + '.' + component, configuration));
152            }
153            return this;
154        }
155    }
156}