001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.camel.util; 018 019import java.net.URI; 020import java.net.URL; 021import java.util.ArrayList; 022import java.util.LinkedHashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.regex.Matcher; 026import java.util.regex.Pattern; 027 028/** 029 * A helper class for <a href="http://json-schema.org/">JSON schema</a>. 030 */ 031public final class JsonSchemaHelper { 032 033 // 0 = text, 1 = enum, 2 = boolean, 3 = integer or number 034 private static final Pattern PATTERN = Pattern.compile("\"(.+?)\"|\\[(.+)\\]|(true|false)|(-?\\d+\\.?\\d*)"); 035 private static final String QUOT = """; 036 037 private JsonSchemaHelper() { 038 } 039 040 /** 041 * Gets the JSon schema type. 042 * 043 * @param type the java type 044 * @return the json schema type, is never null, but returns <tt>object</tt> as the generic type 045 */ 046 public static String getType(Class<?> type) { 047 if (type.isEnum()) { 048 return "enum"; 049 } else if (type.isArray()) { 050 return "array"; 051 } 052 if (type.isAssignableFrom(URI.class) || type.isAssignableFrom(URL.class)) { 053 return "string"; 054 } 055 056 String primitive = getPrimitiveType(type.getCanonicalName()); 057 if (primitive != null) { 058 return primitive; 059 } 060 061 return "object"; 062 } 063 064 /** 065 * Gets the JSon schema primitive type. 066 * 067 * @param name the java type 068 * @return the json schema primitive type, or <tt>null</tt> if not a primitive 069 */ 070 public static String getPrimitiveType(String name) { 071 072 // special for byte[] or Object[] as its common to use 073 if ("java.lang.byte[]".equals(name) || "byte[]".equals(name)) { 074 return "string"; 075 } else if ("java.lang.Byte[]".equals(name) || "Byte[]".equals(name)) { 076 return "array"; 077 } else if ("java.lang.Object[]".equals(name) || "Object[]".equals(name)) { 078 return "array"; 079 } else if ("java.lang.String[]".equals(name) || "String[]".equals(name)) { 080 return "array"; 081 } else if ("java.lang.Character".equals(name) || "Character".equals(name) || "char".equals(name)) { 082 return "string"; 083 } else if ("java.lang.String".equals(name) || "String".equals(name)) { 084 return "string"; 085 } else if ("java.lang.Boolean".equals(name) || "Boolean".equals(name) || "boolean".equals(name)) { 086 return "boolean"; 087 } else if ("java.lang.Integer".equals(name) || "Integer".equals(name) || "int".equals(name)) { 088 return "integer"; 089 } else if ("java.lang.Long".equals(name) || "Long".equals(name) || "long".equals(name)) { 090 return "integer"; 091 } else if ("java.lang.Short".equals(name) || "Short".equals(name) || "short".equals(name)) { 092 return "integer"; 093 } else if ("java.lang.Byte".equals(name) || "Byte".equals(name) || "byte".equals(name)) { 094 return "integer"; 095 } else if ("java.lang.Float".equals(name) || "Float".equals(name) || "float".equals(name)) { 096 return "number"; 097 } else if ("java.lang.Double".equals(name) || "Double".equals(name) || "double".equals(name)) { 098 return "number"; 099 } 100 101 return null; 102 } 103 104 /** 105 * Parses the json schema to split it into a list or rows, where each row contains key value pairs with the metadata 106 * 107 * @param group the group to parse from such as <tt>component</tt>, <tt>componentProperties</tt>, or <tt>properties</tt>. 108 * @param json the json 109 * @return a list of all the rows, where each row is a set of key value pairs with metadata 110 */ 111 public static List<Map<String, String>> parseJsonSchema(String group, String json, boolean parseProperties) { 112 List<Map<String, String>> answer = new ArrayList<Map<String, String>>(); 113 if (json == null) { 114 return answer; 115 } 116 117 boolean found = false; 118 119 // parse line by line 120 String[] lines = json.split("\n"); 121 for (String line : lines) { 122 // we need to find the group first 123 if (!found) { 124 String s = line.trim(); 125 found = s.startsWith("\"" + group + "\":") && s.endsWith("{"); 126 continue; 127 } 128 129 // we should stop when we end the group 130 if (line.equals(" },") || line.equals(" }")) { 131 break; 132 } 133 134 // need to safe encode \" so we can parse the line 135 line = line.replaceAll("\"\\\\\"\"", '"' + QUOT + '"'); 136 137 Map<String, String> row = new LinkedHashMap<String, String>(); 138 Matcher matcher = PATTERN.matcher(line); 139 140 String key; 141 if (parseProperties) { 142 // when parsing properties the first key is given as name, so the first parsed token is the value of the name 143 key = "name"; 144 } else { 145 key = null; 146 } 147 while (matcher.find()) { 148 if (key == null) { 149 key = matcher.group(1); 150 } else { 151 String value = matcher.group(1); 152 if (value != null) { 153 // its text based 154 value = value.trim(); 155 // decode 156 value = value.replaceAll(QUOT, "\""); 157 value = decodeJson(value); 158 } 159 if (value == null) { 160 // not text then its maybe an enum? 161 value = matcher.group(2); 162 if (value != null) { 163 // its an enum so strip out " and trim spaces after comma 164 value = value.replaceAll("\"", ""); 165 value = value.replaceAll(", ", ","); 166 value = value.trim(); 167 } 168 } 169 if (value == null) { 170 // not text then its maybe a boolean? 171 value = matcher.group(3); 172 } 173 if (value == null) { 174 // not text then its maybe a integer? 175 value = matcher.group(4); 176 } 177 if (value != null) { 178 row.put(key, value); 179 } 180 // reset 181 key = null; 182 } 183 } 184 if (!row.isEmpty()) { 185 answer.add(row); 186 } 187 } 188 189 return answer; 190 } 191 192 private static String decodeJson(String value) { 193 // json encodes a \ as \\ so we need to decode from \\ back to \ 194 if ("\\\\".equals(value)) { 195 value = "\\"; 196 } 197 return value; 198 } 199 200 /** 201 * Is the property required 202 * 203 * @param rows the rows of properties 204 * @param name name of the property 205 * @return <tt>true</tt> if required, or <tt>false</tt> if not 206 */ 207 public static boolean isPropertyRequired(List<Map<String, String>> rows, String name) { 208 for (Map<String, String> row : rows) { 209 boolean required = false; 210 boolean found = false; 211 if (row.containsKey("name")) { 212 found = name.equals(row.get("name")); 213 } 214 if (row.containsKey("required")) { 215 required = "true".equals(row.get("required")); 216 } 217 if (found) { 218 return required; 219 } 220 } 221 return false; 222 } 223 224 /** 225 * Gets the default value of the property 226 * 227 * @param rows the rows of properties 228 * @param name name of the property 229 * @return the default value or <tt>null</tt> if no default value exists 230 */ 231 public static String getPropertyDefaultValue(List<Map<String, String>> rows, String name) { 232 for (Map<String, String> row : rows) { 233 String defaultValue = null; 234 boolean found = false; 235 if (row.containsKey("name")) { 236 found = name.equals(row.get("name")); 237 } 238 if (row.containsKey("defaultValue")) { 239 defaultValue = row.get("defaultValue"); 240 } 241 if (found) { 242 return defaultValue; 243 } 244 } 245 return null; 246 } 247 248 /** 249 * Is the property multi valued 250 * 251 * @param rows the rows of properties 252 * @param name name of the property 253 * @return <tt>true</tt> if multi valued, or <tt>false</tt> if not 254 */ 255 public static boolean isPropertyMultiValue(List<Map<String, String>> rows, String name) { 256 for (Map<String, String> row : rows) { 257 boolean multiValue = false; 258 boolean found = false; 259 if (row.containsKey("name")) { 260 found = name.equals(row.get("name")); 261 } 262 if (row.containsKey("multiValue")) { 263 multiValue = "true".equals(row.get("multiValue")); 264 } 265 if (found) { 266 return multiValue; 267 } 268 } 269 return false; 270 } 271 272 /** 273 * Gets the prefix value of the property 274 * 275 * @param rows the rows of properties 276 * @param name name of the property 277 * @return the prefix value or <tt>null</tt> if no prefix value exists 278 */ 279 public static String getPropertyPrefix(List<Map<String, String>> rows, String name) { 280 for (Map<String, String> row : rows) { 281 String prefix = null; 282 boolean found = false; 283 if (row.containsKey("name")) { 284 found = name.equals(row.get("name")); 285 } 286 if (row.containsKey("prefix")) { 287 prefix = row.get("prefix"); 288 } 289 if (found) { 290 return prefix; 291 } 292 } 293 return null; 294 } 295 296}