001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2010–2011 ApexIdentity Inc. 015 * Portions Copyright 2011-2015 ForgeRock AS. 016 */ 017 018package org.forgerock.http.util; 019 020import static com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_COMMENTS; 021import static com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_SINGLE_QUOTES; 022import static com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS; 023import static java.lang.String.format; 024 025import java.io.IOException; 026import java.io.InputStream; 027import java.io.InputStreamReader; 028import java.io.Reader; 029import java.io.StringReader; 030import java.util.Arrays; 031import java.util.List; 032import java.util.Map; 033 034import com.fasterxml.jackson.databind.ObjectMapper; 035 036 037/** 038 * Provides read and write JSON capabilities. 039 * Can check if an object reference is JSON-compatible (expressed as primitive values, list/array and map). 040 */ 041public final class Json { 042 043 /** Non strict object mapper / data binder used to read json configuration files/data. */ 044 private static final ObjectMapper LENIENT_MAPPER; 045 static { 046 LENIENT_MAPPER = new ObjectMapper(); 047 LENIENT_MAPPER.configure(ALLOW_COMMENTS, true); 048 LENIENT_MAPPER.configure(ALLOW_SINGLE_QUOTES, true); 049 LENIENT_MAPPER.configure(ALLOW_UNQUOTED_CONTROL_CHARS, true); 050 } 051 052 /** Strict object mapper / data binder used to read json configuration files/data. */ 053 private static final ObjectMapper STRICT_MAPPER = new ObjectMapper(); 054 055 /** 056 * Private constructor for utility class. 057 */ 058 private Json() { } 059 060 /** 061 * Verify that the given parameter object is of a JSON compatible type (recursively). If no exception is thrown that 062 * means the parameter can be used in the JWT session (that is a JSON value). 063 * 064 * @param trail 065 * pointer to the verified object 066 * @param value 067 * object to verify 068 */ 069 public static void checkJsonCompatibility(final String trail, final Object value) { 070 071 // Null is OK 072 if (value == null) { 073 return; 074 } 075 076 Class<?> type = value.getClass(); 077 Object object = value; 078 079 // JSON supports Boolean 080 if (object instanceof Boolean) { 081 return; 082 } 083 084 // JSON supports Chars (as String) 085 if (object instanceof Character) { 086 return; 087 } 088 089 // JSON supports Numbers (Long, Float, ...) 090 if (object instanceof Number) { 091 return; 092 } 093 094 // JSON supports String 095 if (object instanceof CharSequence) { 096 return; 097 } 098 099 // Consider array like a List 100 if (type.isArray()) { 101 object = Arrays.asList((Object[]) value); 102 } 103 104 if (object instanceof List) { 105 List<?> list = (List<?>) object; 106 for (int i = 0; i < list.size(); i++) { 107 checkJsonCompatibility(format("%s[%d]", trail, i), list.get(i)); 108 } 109 return; 110 } 111 112 if (object instanceof Map) { 113 Map<?, ?> map = (Map<?, ?>) object; 114 for (Map.Entry<?, ?> entry : map.entrySet()) { 115 checkJsonCompatibility(format("%s/%s", trail, entry.getKey()), entry.getValue()); 116 } 117 return; 118 } 119 120 throw new IllegalArgumentException(format( 121 "The object referenced through '%s' cannot be safely serialized as JSON", 122 trail)); 123 } 124 125 /** 126 * Parses to json the provided data. 127 * 128 * @param rawData 129 * The data as a string to read and parse. 130 * @see Json#readJson(Reader) 131 * @return Any of {@code Map<String, Object>}, {@code List<Object>}, {@code Number}, {@code Boolean} 132 * or {@code null}. 133 * @throws IOException 134 * If an exception occurs during parsing the data. 135 */ 136 public static Object readJson(final String rawData) throws IOException { 137 if (rawData == null) { 138 return null; 139 } 140 return readJson(new StringReader(rawData)); 141 } 142 143 /** 144 * Parses to json the provided reader. 145 * 146 * @param reader 147 * The data to parse. 148 * @return Any of {@code Map<String, Object>}, {@code List<Object>}, {@code Number}, {@code Boolean} 149 * or {@code null}. 150 * @throws IOException 151 * If an exception occurs during parsing the data. 152 */ 153 public static Object readJson(final Reader reader) throws IOException { 154 return parse(STRICT_MAPPER, reader); 155 } 156 157 /** 158 * This function it's only used to read our configuration files and allows 159 * JSON files to contain non strict JSON such as comments or single quotes. 160 * 161 * @param reader 162 * The stream of data to parse. 163 * @return Any of {@code Map<String, Object>}, {@code List<Object>}, {@code Number}, {@code Boolean} 164 * or {@code null}. 165 * @throws IOException 166 * If an error occurs during reading/parsing the data. 167 */ 168 public static Object readJsonLenient(final Reader reader) throws IOException { 169 return parse(LENIENT_MAPPER, reader); 170 } 171 172 /** 173 * This function it's only used to read our configuration files and allows 174 * JSON files to contain non strict JSON such as comments or single quotes. 175 * 176 * @param in 177 * The input stream containing the json. 178 * @return Any of {@code Map<String, Object>}, {@code List<Object>}, {@code Number}, {@code Boolean} 179 * or {@code null}. 180 * @throws IOException 181 * If an error occurs during reading/parsing the data. 182 */ 183 public static Object readJsonLenient(final InputStream in) throws IOException { 184 return parse(LENIENT_MAPPER, new InputStreamReader(in)); 185 } 186 187 private static Object parse(ObjectMapper mapper, Reader reader) throws IOException { 188 if (reader == null) { 189 return null; 190 } 191 192 return mapper.readValue(reader, Object.class); 193 } 194 195 /** 196 * Writes the JSON content of the object passed in parameter. 197 * 198 * @param objectToWrite 199 * The object we want to serialize as JSON output. The 200 * @return the Json output as a byte array. 201 * @throws IOException 202 * If an error occurs during writing/mapping content. 203 */ 204 public static byte[] writeJson(final Object objectToWrite) throws IOException { 205 return STRICT_MAPPER.writeValueAsBytes(objectToWrite); 206 } 207}