001package com.nimbusds.common.ldap; 002 003 004import java.util.*; 005import java.util.stream.Collectors; 006 007import com.unboundid.ldap.sdk.Attribute; 008import com.unboundid.ldap.sdk.Entry; 009import com.unboundid.ldap.sdk.SearchResult; 010import com.unboundid.ldap.sdk.SearchResultReference; 011import com.unboundid.ldap.sdk.schema.AttributeSyntaxDefinition; 012import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition; 013import com.unboundid.ldap.sdk.schema.AttributeUsage; 014import com.unboundid.ldap.sdk.schema.MatchingRuleDefinition; 015import com.unboundid.ldap.sdk.schema.MatchingRuleUseDefinition; 016import com.unboundid.ldap.sdk.schema.ObjectClassDefinition; 017import com.unboundid.ldap.sdk.schema.ObjectClassType; 018import com.unboundid.util.Base64; 019 020 021/** 022 * Static methods to format complex LDAP result structures as JSON. 023 * 024 * <p>See the related {@link LDIFResultFormatter} class for LDIF result 025 * formatting. 026 */ 027public class JSONResultFormatter { 028 029 030 /** 031 * Formats an LDAP directory entry as a JSON object. 032 * 033 * <p>Format: 034 * 035 * <pre> 036 * { 037 * "DN" : "uid=user001,ou=people,dc=example,dc=com", 038 * "attribute-name-1" : [value-1, value-2, value-3, ...], 039 * "attribute-name-2" : [value-1, value-2, ...], 040 * "attribute-name-3" : [value-1, ...], 041 * ... 042 * } 043 * </pre> 044 * 045 * @param entry The directory entry. Must not be {@code null}. 046 * @param binary The name of the attributes to Base64 encode. Must 047 * not be {@code null}. 048 * @param omitDN If {@code true} the DN will be omitted from the 049 * returned map. 050 * @param normalize If {@code true} attribute names will be converted 051 * to lower case. 052 * 053 * @return A JSON object representing the directory entry. 054 */ 055 public static Map<String,Object> formatEntry(final Entry entry, 056 final Set<String> binary, 057 final boolean omitDN, 058 final boolean normalize) { 059 060 Map<String,Object> jsonObject = new LinkedHashMap<>(); 061 062 if (! omitDN) 063 jsonObject.put("DN", entry.getDN()); 064 065 Collection<Attribute>attributes = entry.getAttributes(); 066 067 for (final Attribute a : attributes) { 068 069 List<String> values = new LinkedList<>(); 070 071 // Does the attribute require BASE-64 encoding 072 if (binary.contains(a.getName().toLowerCase())) { 073 074 // Apply BASE64 encoding if the attribute 075 // contains at least one binary value 076 077 for (final byte[] binVal: a.getValueByteArrays()) 078 values.add(Base64.encode(binVal)); 079 } 080 else { 081 // Default UTF-8 encoding (from LDAP SDK) 082 Collections.addAll(values, a.getValues()); 083 } 084 085 String name = a.getBaseName(); 086 087 if (normalize) 088 name = name.toLowerCase(); 089 090 jsonObject.put(name, values); 091 } 092 093 return jsonObject; 094 } 095 096 097 /** 098 * Formats an LDAP directory entry as a JSON object. 099 * 100 * <p>Format: 101 * 102 * <pre> 103 * { 104 * "DN" : "uid=user001,ou=people,dc=example,dc=com", 105 * "attribute-name-1" : [value-1, value-2, value-3, ...], 106 * "attribute-name-2" : [value-1, value-2, ...], 107 * "attribute-name-3" : [value-1, ...], 108 * ... 109 * } 110 * </pre> 111 * 112 * @param entry The directory entry. Must not be {@code null}. 113 * @param binary The name of the attributes to Base64 encode. Must 114 * not be {@code null}. 115 * @param normalize If {@code true} attribute names will be converted 116 * to lower case. 117 * 118 * @return A JSON object representing the directory entry. 119 */ 120 public static Map<String,Object> formatEntry(final Entry entry, 121 final Set<String> binary, 122 final boolean normalize) { 123 124 return formatEntry(entry, binary, false, normalize); 125 } 126 127 128 /** 129 * Formats a LDAP search result as a JSON object containing matches 130 * and referrals. 131 * 132 * <p>Format: 133 * 134 * <pre> 135 * { 136 * "matches" : [ { entry-1 }, { entry-2 }, { entry-3 }, ...], 137 * "referrals" : [ "url-1", "url-2", "url-3", ...], 138 * "page" : { "totalEntryCount" : n, 139 * "more" : true|false, 140 * "cookie" : "..." }, 141 * "vlv" : { "totalEntryCount" : n, 142 * "offset" : n, 143 * "cookie" : "..." } 144 * } 145 * </pre> 146 * 147 * where {@code entry-n} are formatted by the {@link #formatEntry} 148 * method. 149 * 150 * @param sr The search result. Must not be {@code null}. 151 * @param binary The name of the attributes to Base64 encode. Must 152 * not be {@code null}. 153 * @param normalize If {@code true} attribute names will be converted 154 * to lower case. 155 * 156 * @return A JSON object containing the search result matches, 157 * referrals and optional control data. 158 */ 159 public static Map<String,Object> formatSearchResult(final SearchResult sr, 160 final Set<String> binary, 161 final boolean normalize) { 162 163 Map<String,Object> jsonObject = new LinkedHashMap<>(); 164 165 // A result consists of possible matching entries and referrals 166 167 List<Map<String,Object>> matches = sr.getSearchEntries() 168 .stream() 169 .map(entry -> JSONResultFormatter.formatEntry(entry, binary, normalize)) 170 .collect(Collectors.toCollection(LinkedList::new)); 171 172 jsonObject.put("matches", matches); 173 174 List<String> referrals = new LinkedList<>(); 175 176 for (SearchResultReference ref: sr.getSearchReferences()) 177 Collections.addAll(referrals, ref.getReferralURLs()); 178 179 jsonObject.put("referrals", referrals); 180 181 // Append any simple-page control or virtual-list-view control results 182 LDAPControlResultFormatter.appendSearchControlResults(jsonObject, sr); 183 184 return jsonObject; 185 } 186 187 188 /** 189 * Formats an object class defintion. 190 * 191 * @param def The object class definition. Must not be {@code null}. 192 * 193 * @return The object class properties as JSON object. 194 */ 195 public static Map<String,Object> formatObjectClass(final ObjectClassDefinition def) { 196 197 Map<String,Object> jsonObject = new LinkedHashMap<>(); 198 199 String oid = def.getOID(); 200 jsonObject.put("OID", oid); 201 202 List<String> names = Arrays.asList(def.getNames()); 203 jsonObject.put("names", names); 204 205 String description = def.getDescription(); 206 jsonObject.put("description", description); 207 208 boolean obsolete = def.isObsolete(); 209 jsonObject.put("obsolete", obsolete); 210 211 212 ObjectClassType type = def.getObjectClassType(); 213 214 if (type != null) 215 jsonObject.put("type", type.toString()); 216 else 217 jsonObject.put("type", null); 218 219 220 List<String> requiredAttributes = Arrays.asList(def.getRequiredAttributes()); 221 jsonObject.put("requiredAttributes", requiredAttributes); 222 223 List<String> optionalAttributes = Arrays.asList(def.getOptionalAttributes()); 224 jsonObject.put("optionalAttributes", optionalAttributes); 225 226 List<String> superClasses = Arrays.asList(def.getSuperiorClasses()); 227 jsonObject.put("superClasses", superClasses); 228 229 String rawDefinition = def.toString(); 230 jsonObject.put("rawDefinition", rawDefinition); 231 232 return jsonObject; 233 } 234 235 236 /** 237 * Formats an attribute type defintion. 238 * 239 * @param def The attribute type definition. Must not be {@code null}. 240 * 241 * @return The attribute type properties as JSON object. 242 */ 243 public static Map<String,Object> formatAttributeType(final AttributeTypeDefinition def) { 244 245 Map<String,Object> jsonObject = new LinkedHashMap<>(); 246 247 // details 248 249 String oid = def.getOID(); 250 jsonObject.put("OID", oid); 251 252 List<String> names = Arrays.asList(def.getNames()); 253 jsonObject.put("names", names); 254 255 String description = def.getDescription(); 256 jsonObject.put("description", description); 257 258 AttributeUsage usage = def.getUsage(); 259 jsonObject.put("usage", usage.toString()); 260 261 262 // flags 263 264 boolean obsolete = def.isObsolete(); 265 jsonObject.put("obsolete", obsolete); 266 267 boolean singleValued = def.isSingleValued(); 268 jsonObject.put("singleValued", singleValued); 269 270 boolean readOnly = def.isNoUserModification(); 271 jsonObject.put("readOnly", readOnly); 272 273 boolean collective = def.isCollective(); 274 jsonObject.put("collective", collective); 275 276 277 // syntax 278 279 String syntaxOID = def.getSyntaxOID(); 280 jsonObject.put("syntaxOID", syntaxOID); 281 282 283 // matching rules 284 285 String equalityRule = def.getEqualityMatchingRule(); 286 jsonObject.put("equalityMatch", equalityRule); 287 288 String orderRule = def.getOrderingMatchingRule(); 289 jsonObject.put("orderingMatch", orderRule); 290 291 String substringRule = def.getSubstringMatchingRule(); 292 jsonObject.put("substringMatch", substringRule); 293 294 295 // supertype 296 String superType = def.getSuperiorType(); 297 jsonObject.put("superType", superType); 298 299 300 // raw schema definition 301 String rawDefinition = def.toString(); 302 jsonObject.put("rawDefinition", rawDefinition); 303 304 return jsonObject; 305 } 306 307 308 /** 309 * Formats a matching rule defintion. 310 * 311 * @param def The matching rule definition. Must not be {@code null}. 312 * 313 * @return The matching rule properties as JSON object. 314 */ 315 public static Map<String,Object> formatMatchingRule(final MatchingRuleDefinition def) { 316 317 Map<String,Object> jsonObject = new LinkedHashMap<>(); 318 319 // details 320 321 String oid = def.getOID(); 322 jsonObject.put("OID", oid); 323 324 List<String> names = Arrays.asList(def.getNames()); 325 jsonObject.put("names", names); 326 327 String description = def.getDescription(); 328 jsonObject.put("description", description); 329 330 331 // flags 332 boolean obsolete = def.isObsolete(); 333 jsonObject.put("obsolete", obsolete); 334 335 336 // syntax 337 String syntaxOID = def.getSyntaxOID(); 338 jsonObject.put("syntaxOID", syntaxOID); 339 340 341 // raw schema definition 342 String rawDefinition = def.toString(); 343 jsonObject.put("rawDefinition", rawDefinition); 344 345 return jsonObject; 346 } 347 348 349 /** 350 * Formats a matching rule use defintion. 351 * 352 * @param def The matching rule use definition. Must not be 353 * {@code null}. 354 * 355 * @return The matching rule use properties as JSON object. 356 */ 357 public static Map<String,Object> formatMatchingRuleUse(final MatchingRuleUseDefinition def) { 358 359 Map<String,Object> jsonObject = new LinkedHashMap<>(); 360 361 // details 362 363 String oid = def.getOID(); 364 jsonObject.put("OID", oid); 365 366 List<String> names = Arrays.asList(def.getNames()); 367 jsonObject.put("names", names); 368 369 String description = def.getDescription(); 370 jsonObject.put("description", description); 371 372 373 // flags 374 boolean obsolete = def.isObsolete(); 375 jsonObject.put("obsolete", obsolete); 376 377 378 // applicable attribute types 379 List<String> applicableTypes = Arrays.asList(def.getApplicableAttributeTypes()); 380 jsonObject.put("applicableTypes", applicableTypes); 381 382 383 // raw schema definition 384 String rawDefinition = def.toString(); 385 jsonObject.put("rawDefinition", rawDefinition); 386 387 return jsonObject; 388 } 389 390 391 /** 392 * Formats an attribute syntax defintion. 393 * 394 * @param def The attribute syntax definition. Must not be 395 * {@code null}. 396 * 397 * @return The syntax properties as JSON object. 398 */ 399 public static Map<String,Object> formatSyntax(final AttributeSyntaxDefinition def) { 400 401 Map<String,Object> jsonObject = new LinkedHashMap<>(); 402 403 // details 404 405 String oid = def.getOID(); 406 jsonObject.put("OID", oid); 407 408 String description = def.getDescription(); 409 jsonObject.put("description", description); 410 411 412 // raw schema definition 413 String rawDefinition = def.toString(); 414 jsonObject.put("rawDefinition", rawDefinition); 415 416 return jsonObject; 417 } 418 419 420 /** 421 * Prevents instantiation. 422 */ 423 private JSONResultFormatter() { 424 425 // Nothing to do 426 } 427}