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}