001 /*
002 * Copyright 2009-2015 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2015 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021 package com.unboundid.ldap.sdk.unboundidds.controls;
022
023
024
025 import java.util.ArrayList;
026 import java.util.Collections;
027 import java.util.Iterator;
028 import java.util.List;
029
030 import com.unboundid.asn1.ASN1Element;
031 import com.unboundid.asn1.ASN1Enumerated;
032 import com.unboundid.asn1.ASN1OctetString;
033 import com.unboundid.asn1.ASN1Sequence;
034 import com.unboundid.ldap.sdk.Control;
035 import com.unboundid.ldap.sdk.DecodeableControl;
036 import com.unboundid.ldap.sdk.LDAPException;
037 import com.unboundid.ldap.sdk.ResultCode;
038 import com.unboundid.ldap.sdk.SearchResultEntry;
039 import com.unboundid.util.NotMutable;
040 import com.unboundid.util.ThreadSafety;
041 import com.unboundid.util.ThreadSafetyLevel;
042
043 import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
044 import static com.unboundid.util.Debug.*;
045 import static com.unboundid.util.StaticUtils.*;
046 import static com.unboundid.util.Validator.*;
047
048
049
050 /**
051 * <BLOCKQUOTE>
052 * <B>NOTE:</B> This class is part of the Commercial Edition of the UnboundID
053 * LDAP SDK for Java. It is not available for use in applications that
054 * include only the Standard Edition of the LDAP SDK, and is not supported for
055 * use in conjunction with non-UnboundID products.
056 * </BLOCKQUOTE>
057 * This class provides an implementation of a control that may be included in a
058 * search result entry in response to a join request control to provide a set of
059 * entries related to the search result entry. See the class-level
060 * documentation for the {@link JoinRequestControl} class for additional
061 * information and an example demonstrating its use.
062 * <BR><BR>
063 * The value of the join result control is encoded as follows:
064 * <PRE>
065 * JoinResult ::= SEQUENCE {
066 * COMPONENTS OF LDAPResult,
067 * entries [4] SEQUENCE OF JoinedEntry }
068 * </PRE>
069 */
070 @NotMutable()
071 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
072 public final class JoinResultControl
073 extends Control
074 implements DecodeableControl
075 {
076 /**
077 * The OID (1.3.6.1.4.1.30221.2.5.9) for the join result control.
078 */
079 public static final String JOIN_RESULT_OID = "1.3.6.1.4.1.30221.2.5.9";
080
081
082
083 /**
084 * The BER type for the referral URLs element.
085 */
086 private static final byte TYPE_REFERRAL_URLS = (byte) 0xA3;
087
088
089
090 /**
091 * The BER type for the join results element.
092 */
093 private static final byte TYPE_JOIN_RESULTS = (byte) 0xA4;
094
095
096
097 /**
098 * The serial version UID for this serializable class.
099 */
100 private static final long serialVersionUID = 681831114773253358L;
101
102
103
104 // The set of entries which have been joined with the associated search result
105 // entry.
106 private final List<JoinedEntry> joinResults;
107
108 // The set of referral URLs for this join result.
109 private final List<String> referralURLs;
110
111 // The result code for this join result.
112 private final ResultCode resultCode;
113
114 // The diagnostic message for this join result.
115 private final String diagnosticMessage;
116
117 // The matched DN for this join result.
118 private final String matchedDN;
119
120
121
122 /**
123 * Creates a new empty control instance that is intended to be used only for
124 * decoding controls via the {@code DecodeableControl} interface.
125 */
126 JoinResultControl()
127 {
128 resultCode = null;
129 diagnosticMessage = null;
130 matchedDN = null;
131 referralURLs = null;
132 joinResults = null;
133 }
134
135
136
137 /**
138 * Creates a new join result control indicating a successful join.
139 *
140 * @param joinResults The set of entries that have been joined with the
141 * associated search result entry. It may be
142 * {@code null} or empty if no entries were joined with
143 * the search result entry.
144 */
145 public JoinResultControl(final List<JoinedEntry> joinResults)
146 {
147 this(ResultCode.SUCCESS, null, null, null, joinResults);
148 }
149
150
151
152 /**
153 * Creates a new join result control with the provided information.
154 *
155 * @param resultCode The result code for the join processing. It
156 * must not be {@code null}.
157 * @param diagnosticMessage A message with additional information about the
158 * result of the join processing. It may be
159 * {@code null} if no message is needed.
160 * @param matchedDN The matched DN for the join processing. It may
161 * be {@code null} if no matched DN is needed.
162 * @param referralURLs The set of referral URLs for any referrals
163 * encountered while processing the join. It may
164 * be {@code null} or empty if no referral URLs
165 * are needed.
166 * @param joinResults The set of entries that have been joined with
167 * associated search result entry. It may be
168 * {@code null} or empty if no entries were joined
169 * with the search result entry.
170 */
171 public JoinResultControl(final ResultCode resultCode,
172 final String diagnosticMessage, final String matchedDN,
173 final List<String> referralURLs,
174 final List<JoinedEntry> joinResults)
175 {
176 super(JOIN_RESULT_OID, false,
177 encodeValue(resultCode, diagnosticMessage, matchedDN, referralURLs,
178 joinResults));
179
180 this.resultCode = resultCode;
181 this.diagnosticMessage = diagnosticMessage;
182 this.matchedDN = matchedDN;
183
184 if (referralURLs == null)
185 {
186 this.referralURLs = Collections.emptyList();
187 }
188 else
189 {
190 this.referralURLs = Collections.unmodifiableList(referralURLs);
191 }
192
193 if (joinResults == null)
194 {
195 this.joinResults = Collections.emptyList();
196 }
197 else
198 {
199 this.joinResults = Collections.unmodifiableList(joinResults);
200 }
201 }
202
203
204
205 /**
206 * Creates a new join result control with the provided information.
207 *
208 * @param oid The OID for the control.
209 * @param isCritical Indicates whether the control should be marked
210 * critical.
211 * @param value The encoded value for the control. This may be
212 * {@code null} if no value was provided.
213 *
214 * @throws LDAPException If the provided control cannot be decoded as an
215 * account usable response control.
216 */
217 public JoinResultControl(final String oid, final boolean isCritical,
218 final ASN1OctetString value)
219 throws LDAPException
220 {
221 super(oid, isCritical, value);
222
223 if (value == null)
224 {
225 throw new LDAPException(ResultCode.DECODING_ERROR,
226 ERR_JOIN_RESULT_NO_VALUE.get());
227 }
228
229 try
230 {
231 final ASN1Element valueElement = ASN1Element.decode(value.getValue());
232 final ASN1Element[] elements =
233 ASN1Sequence.decodeAsSequence(valueElement).elements();
234
235 resultCode = ResultCode.valueOf(
236 ASN1Enumerated.decodeAsEnumerated(elements[0]).intValue());
237
238 final String matchedDNStr =
239 ASN1OctetString.decodeAsOctetString(elements[1]).stringValue();
240 if (matchedDNStr.length() == 0)
241 {
242 matchedDN = null;
243 }
244 else
245 {
246 matchedDN = matchedDNStr;
247 }
248
249 final String diagnosticMessageStr =
250 ASN1OctetString.decodeAsOctetString(elements[2]).stringValue();
251 if (diagnosticMessageStr.length() == 0)
252 {
253 diagnosticMessage = null;
254 }
255 else
256 {
257 diagnosticMessage = diagnosticMessageStr;
258 }
259
260 final ArrayList<String> refs = new ArrayList<String>();
261 final ArrayList<JoinedEntry> entries = new ArrayList<JoinedEntry>();
262 for (int i=3; i < elements.length; i++)
263 {
264 switch (elements[i].getType())
265 {
266 case TYPE_REFERRAL_URLS:
267 final ASN1Element[] refElements =
268 ASN1Sequence.decodeAsSequence(elements[i]).elements();
269 for (final ASN1Element e : refElements)
270 {
271 refs.add(ASN1OctetString.decodeAsOctetString(e).stringValue());
272 }
273 break;
274
275 case TYPE_JOIN_RESULTS:
276 final ASN1Element[] entryElements =
277 ASN1Sequence.decodeAsSequence(elements[i]).elements();
278 for (final ASN1Element e : entryElements)
279 {
280 entries.add(JoinedEntry.decode(e));
281 }
282 break;
283
284 default:
285 throw new LDAPException(ResultCode.DECODING_ERROR,
286 ERR_JOIN_RESULT_INVALID_ELEMENT_TYPE.get(
287 toHex(elements[i].getType())));
288 }
289 }
290
291 referralURLs = Collections.unmodifiableList(refs);
292 joinResults = Collections.unmodifiableList(entries);
293 }
294 catch (Exception e)
295 {
296 debugException(e);
297
298 throw new LDAPException(ResultCode.DECODING_ERROR,
299 ERR_JOIN_RESULT_CANNOT_DECODE.get(getExceptionMessage(e)), e);
300 }
301 }
302
303
304
305 /**
306 * Encodes the provided information as appropriate for use as the value of
307 * this control.
308 *
309 * @param resultCode The result code for the join processing. It
310 * must not be {@code null}.
311 * @param diagnosticMessage A message with additional information about the
312 * result of the join processing. It may be
313 * {@code null} if no message is needed.
314 * @param matchedDN The matched DN for the join processing. It may
315 * be {@code null} if no matched DN is needed.
316 * @param referralURLs The set of referral URLs for any referrals
317 * encountered while processing the join. It may
318 * be {@code null} or empty if no referral URLs
319 * are needed.
320 * @param joinResults The set of entries that have been joined with
321 * associated search result entry. It may be
322 * {@code null} or empty if no entries were joined
323 * with the search result entry.
324 *
325 * @return An ASN.1 element containing an encoded representation of the
326 * value for this control.
327 */
328 private static ASN1OctetString encodeValue(final ResultCode resultCode,
329 final String diagnosticMessage, final String matchedDN,
330 final List<String> referralURLs,
331 final List<JoinedEntry> joinResults)
332 {
333 ensureNotNull(resultCode);
334
335 final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(5);
336 elements.add(new ASN1Enumerated(resultCode.intValue()));
337
338 if (matchedDN == null)
339 {
340 elements.add(new ASN1OctetString());
341 }
342 else
343 {
344 elements.add(new ASN1OctetString(matchedDN));
345 }
346
347 if (diagnosticMessage == null)
348 {
349 elements.add(new ASN1OctetString());
350 }
351 else
352 {
353 elements.add(new ASN1OctetString(diagnosticMessage));
354 }
355
356 if ((referralURLs != null) && (! referralURLs.isEmpty()))
357 {
358 final ArrayList<ASN1Element> refElements =
359 new ArrayList<ASN1Element>(referralURLs.size());
360 for (final String s : referralURLs)
361 {
362 refElements.add(new ASN1OctetString(s));
363 }
364 elements.add(new ASN1Sequence(TYPE_REFERRAL_URLS, refElements));
365 }
366
367 if ((joinResults == null) || joinResults.isEmpty())
368 {
369 elements.add(new ASN1Sequence(TYPE_JOIN_RESULTS));
370 }
371 else
372 {
373 final ArrayList<ASN1Element> entryElements =
374 new ArrayList<ASN1Element>(joinResults.size());
375 for (final JoinedEntry e : joinResults)
376 {
377 entryElements.add(e.encode());
378 }
379 elements.add(new ASN1Sequence(TYPE_JOIN_RESULTS, entryElements));
380 }
381
382 return new ASN1OctetString(new ASN1Sequence(elements).encode());
383 }
384
385
386
387 /**
388 * Retrieves the result code for this join result.
389 *
390 * @return The result code for this join result.
391 */
392 public ResultCode getResultCode()
393 {
394 return resultCode;
395 }
396
397
398
399 /**
400 * Retrieves the diagnostic message for this join result.
401 *
402 * @return The diagnostic message for this join result, or {@code null} if
403 * there is no diagnostic message.
404 */
405 public String getDiagnosticMessage()
406 {
407 return diagnosticMessage;
408 }
409
410
411
412 /**
413 * Retrieves the matched DN for this join result.
414 *
415 * @return The matched DN for this join result, or {@code null} if there is
416 * no matched DN.
417 */
418 public String getMatchedDN()
419 {
420 return matchedDN;
421 }
422
423
424
425 /**
426 * Retrieves the set of referral URLs for this join result.
427 *
428 * @return The set of referral URLs for this join result, or an empty list
429 * if there are no referral URLs.
430 */
431 public List<String> getReferralURLs()
432 {
433 return referralURLs;
434 }
435
436
437
438 /**
439 * Retrieves the set of entries that have been joined with the associated
440 * search result entry.
441 *
442 * @return The set of entries that have been joined with the associated
443 * search result entry.
444 */
445 public List<JoinedEntry> getJoinResults()
446 {
447 return joinResults;
448 }
449
450
451
452 /**
453 * {@inheritDoc}
454 */
455 public JoinResultControl decodeControl(final String oid,
456 final boolean isCritical,
457 final ASN1OctetString value)
458 throws LDAPException
459 {
460 return new JoinResultControl(oid, isCritical, value);
461 }
462
463
464
465 /**
466 * Extracts a join result control from the provided search result entry.
467 *
468 * @param entry The search result entry from which to retrieve the join
469 * result control.
470 *
471 * @return The join result control contained in the provided search result
472 * entry, or {@code null} if the entry did not contain a join result
473 * control.
474 *
475 * @throws LDAPException If a problem is encountered while attempting to
476 * decode the join result control contained in the
477 * provided search result entry.
478 */
479 public static JoinResultControl get(final SearchResultEntry entry)
480 throws LDAPException
481 {
482 final Control c = entry.getControl(JOIN_RESULT_OID);
483 if (c == null)
484 {
485 return null;
486 }
487
488 if (c instanceof JoinResultControl)
489 {
490 return (JoinResultControl) c;
491 }
492 else
493 {
494 return new JoinResultControl(c.getOID(), c.isCritical(), c.getValue());
495 }
496 }
497
498
499
500 /**
501 * {@inheritDoc}
502 */
503 @Override()
504 public String getControlName()
505 {
506 return INFO_CONTROL_NAME_JOIN_RESULT.get();
507 }
508
509
510
511 /**
512 * {@inheritDoc}
513 */
514 @Override()
515 public void toString(final StringBuilder buffer)
516 {
517 buffer.append("JoinResultControl(resultCode='");
518 buffer.append(resultCode.getName());
519 buffer.append("', diagnosticMessage='");
520
521 if (diagnosticMessage != null)
522 {
523 buffer.append(diagnosticMessage);
524 }
525
526 buffer.append("', matchedDN='");
527 if (matchedDN != null)
528 {
529 buffer.append(matchedDN);
530 }
531
532 buffer.append("', referralURLs={");
533 final Iterator<String> refIterator = referralURLs.iterator();
534 while (refIterator.hasNext())
535 {
536 buffer.append(refIterator.next());
537 if (refIterator.hasNext())
538 {
539 buffer.append(", ");
540 }
541 }
542
543 buffer.append("}, joinResults={");
544 final Iterator<JoinedEntry> entryIterator = joinResults.iterator();
545 while (entryIterator.hasNext())
546 {
547 entryIterator.next().toString(buffer);
548 if (entryIterator.hasNext())
549 {
550 buffer.append(", ");
551 }
552 }
553
554 buffer.append("})");
555 }
556 }