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}