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 private static final Pattern PATTERN = Pattern.compile("\"(.+?)\"|\\[(.+)\\]"); 034 private static final String QUOT = """; 035 036 private JsonSchemaHelper() { 037 } 038 039 /** 040 * Gets the JSon schema type. 041 * 042 * @param type the java type 043 * @return the json schema type, is never null, but returns <tt>object</tt> as the generic type 044 */ 045 public static String getType(Class<?> type) { 046 if (type.isEnum()) { 047 return "enum"; 048 } else if (type.isArray()) { 049 return "array"; 050 } 051 if (type.isAssignableFrom(URI.class) || type.isAssignableFrom(URL.class)) { 052 return "sting"; 053 } 054 055 String primitive = getPrimitiveType(type); 056 if (primitive != null) { 057 return primitive; 058 } 059 060 return "object"; 061 } 062 063 /** 064 * Gets the JSon schema primitive type. 065 * 066 * @param type the java type 067 * @return the json schema primitive type, or <tt>null</tt> if not a primitive 068 */ 069 public static String getPrimitiveType(Class<?> type) { 070 String name = type.getCanonicalName(); 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 // and these is common as well 082 } else if ("java.lang.String".equals(name) || "String".equals(name)) { 083 return "string"; 084 } else if ("java.lang.Boolean".equals(name) || "Boolean".equals(name)) { 085 return "boolean"; 086 } else if ("boolean".equals(name)) { 087 return "boolean"; 088 } else if ("java.lang.Integer".equals(name) || "Integer".equals(name)) { 089 return "integer"; 090 } else if ("int".equals(name)) { 091 return "integer"; 092 } else if ("java.lang.Long".equals(name) || "Long".equals(name)) { 093 return "integer"; 094 } else if ("long".equals(name)) { 095 return "integer"; 096 } else if ("java.lang.Short".equals(name) || "Short".equals(name)) { 097 return "integer"; 098 } else if ("short".equals(name)) { 099 return "integer"; 100 } else if ("java.lang.Byte".equals(name) || "Byte".equals(name)) { 101 return "integer"; 102 } else if ("byte".equals(name)) { 103 return "integer"; 104 } else if ("java.lang.Float".equals(name) || "Float".equals(name)) { 105 return "number"; 106 } else if ("float".equals(name)) { 107 return "number"; 108 } else if ("java.lang.Double".equals(name) || "Double".equals(name)) { 109 return "number"; 110 } else if ("double".equals(name)) { 111 return "number"; 112 } 113 114 return null; 115 } 116 117 /** 118 * Parses the json schema to split it into a list or rows, where each row contains key value pairs with the metadata 119 * 120 * @param group the group to parse from such as <tt>component</tt>, <tt>componentProperties</tt>, or <tt>properties</tt>. 121 * @param json the json 122 * @return a list of all the rows, where each row is a set of key value pairs with metadata 123 */ 124 public static List<Map<String, String>> parseJsonSchema(String group, String json, boolean parseProperties) { 125 List<Map<String, String>> answer = new ArrayList<Map<String, String>>(); 126 if (json == null) { 127 return answer; 128 } 129 130 boolean found = false; 131 132 // parse line by line 133 String[] lines = json.split("\n"); 134 for (String line : lines) { 135 // we need to find the group first 136 if (!found) { 137 String s = line.trim(); 138 found = s.startsWith("\"" + group + "\":") && s.endsWith("{"); 139 continue; 140 } 141 142 // we should stop when we end the group 143 if (line.equals(" },") || line.equals(" }")) { 144 break; 145 } 146 147 // need to safe encode \" so we can parse the line 148 line = line.replaceAll("\"\\\\\"\"", '"' + QUOT + '"'); 149 150 Map<String, String> row = new LinkedHashMap<String, String>(); 151 Matcher matcher = PATTERN.matcher(line); 152 153 String key; 154 if (parseProperties) { 155 // when parsing properties the first key is given as name, so the first parsed token is the value of the name 156 key = "name"; 157 } else { 158 key = null; 159 } 160 while (matcher.find()) { 161 if (key == null) { 162 key = matcher.group(1); 163 } else { 164 String value = matcher.group(1); 165 if (value == null) { 166 value = matcher.group(2); 167 // its an enum so strip out " and trim spaces after comma 168 value = value.replaceAll("\"", ""); 169 value = value.replaceAll(", ", ","); 170 } 171 if (value != null) { 172 value = value.trim(); 173 // decode 174 value = value.replaceAll(QUOT, "\""); 175 value = decodeJson(value); 176 } 177 row.put(key, value); 178 // reset 179 key = null; 180 } 181 } 182 if (!row.isEmpty()) { 183 answer.add(row); 184 } 185 } 186 187 return answer; 188 } 189 190 private static String decodeJson(String value) { 191 // json encodes a \ as \\ so we need to decode from \\ back to \ 192 if ("\\\\".equals(value)) { 193 value = "\\"; 194 } 195 return value; 196 } 197 198 /** 199 * Is the property required 200 * 201 * @param rows the rows of properties 202 * @param name name of the property 203 * @return <tt>true</tt> if required, or <tt>false</tt> if not 204 */ 205 public static boolean isPropertyRequired(List<Map<String, String>> rows, String name) { 206 for (Map<String, String> row : rows) { 207 boolean required = false; 208 boolean found = false; 209 if (row.containsKey("name")) { 210 found = name.equals(row.get("name")); 211 } 212 if (row.containsKey("required")) { 213 required = "true".equals(row.get("required")); 214 } 215 if (found) { 216 return required; 217 } 218 } 219 return false; 220 } 221 222 /** 223 * Gets the default value of the property 224 * 225 * @param rows the rows of properties 226 * @param name name of the property 227 * @return the default value or <tt>null</tt> if no default value exists 228 */ 229 public static String getPropertyDefaultValue(List<Map<String, String>> rows, String name) { 230 for (Map<String, String> row : rows) { 231 String defaultValue = null; 232 boolean found = false; 233 if (row.containsKey("name")) { 234 found = name.equals(row.get("name")); 235 } 236 if (row.containsKey("defaultValue")) { 237 defaultValue = row.get("defaultValue"); 238 } 239 if (found) { 240 return defaultValue; 241 } 242 } 243 return null; 244 } 245 246}