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.io.Serializable;
026 import java.util.ArrayList;
027
028 import com.unboundid.asn1.ASN1Boolean;
029 import com.unboundid.asn1.ASN1Element;
030 import com.unboundid.asn1.ASN1Enumerated;
031 import com.unboundid.asn1.ASN1Integer;
032 import com.unboundid.asn1.ASN1OctetString;
033 import com.unboundid.asn1.ASN1Sequence;
034 import com.unboundid.ldap.sdk.DereferencePolicy;
035 import com.unboundid.ldap.sdk.Filter;
036 import com.unboundid.ldap.sdk.LDAPException;
037 import com.unboundid.ldap.sdk.ResultCode;
038 import com.unboundid.ldap.sdk.SearchScope;
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 contains a data structure which provides information about the
058 * value of an LDAP join request control, which may or may not include a nested
059 * join. See the class-level documentation for the {@link JoinRequestControl}
060 * class for additional information and an example demonstrating its use.
061 * <BR><BR>
062 * The value of the join request control is encoded as follows:
063 * <PRE>
064 * LDAPJoin ::= SEQUENCE {
065 * joinRule JoinRule,
066 * baseObject CHOICE {
067 * useSearchBaseDN [0] NULL,
068 * useSourceEntryDN [1] NULL,
069 * useCustomBaseDN [2] LDAPDN,
070 * ... },
071 * scope [0] ENUMERATED {
072 * baseObject (0),
073 * singleLevel (1),
074 * wholeSubtree (2),
075 * subordinateSubtree (3),
076 * ... } OPTIONAL,
077 * derefAliases [1] ENUMERATED {
078 * neverDerefAliases (0),
079 * derefInSearching (1),
080 * derefFindingBaseObj (2),
081 * derefAlways (3),
082 * ... } OPTIONAL,
083 * sizeLimit [2] INTEGER (0 .. maxInt) OPTIONAL,
084 * filter [3] Filter OPTIONAL,
085 * attributes [4] AttributeSelection OPTIONAL,
086 * requireMatch [5] BOOLEAN DEFAULT FALSE,
087 * nestedJoin [6] LDAPJoin OPTIONAL,
088 * ... }
089 * </PRE>
090 */
091 @NotMutable()
092 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
093 public final class JoinRequestValue
094 implements Serializable
095 {
096 /**
097 * The set of attributes that will be used if all user attributes should be
098 * requested.
099 */
100 private static final String[] NO_ATTRIBUTES = NO_STRINGS;
101
102
103
104 /**
105 * The BER type to use for the scope element.
106 */
107 private static final byte TYPE_SCOPE = (byte) 0x80;
108
109
110
111 /**
112 * The BER type to use for the dereference policy element.
113 */
114 private static final byte TYPE_DEREF_POLICY = (byte) 0x81;
115
116
117
118 /**
119 * The BER type to use for the size limit element.
120 */
121 private static final byte TYPE_SIZE_LIMIT = (byte) 0x82;
122
123
124
125 /**
126 * The BER type to use for the filter element.
127 */
128 private static final byte TYPE_FILTER = (byte) 0xA3;
129
130
131
132 /**
133 * The BER type to use for the attributes element.
134 */
135 private static final byte TYPE_ATTRIBUTES = (byte) 0xA4;
136
137
138
139 /**
140 * The BER type to use for the require match element.
141 */
142 private static final byte TYPE_REQUIRE_MATCH = (byte) 0x85;
143
144
145
146 /**
147 * The BER type to use for the nested join element.
148 */
149 private static final byte TYPE_NESTED_JOIN = (byte) 0xA6;
150
151
152
153 /**
154 * The serial version UID for this serializable class.
155 */
156 private static final long serialVersionUID = 4675881185117657177L;
157
158
159
160 // Indicates whether to require at least one entry to match the join
161 // criteria for the entry to be returned.
162 private final boolean requireMatch;
163
164 // The dereference policy for this join request value.
165 private final DereferencePolicy derefPolicy;
166
167 // The filter for this join request value.
168 private final Filter filter;
169
170 // The size limit for this join request value.
171 private final Integer sizeLimit;
172
173 // The base DN to use for this join request value.
174 private final JoinBaseDN baseDN;
175
176 // The nested join criteria for this join request value.
177 private final JoinRequestValue nestedJoin;
178
179 // The join rule for this join request value.
180 private final JoinRule joinRule;
181
182 // The scope for this join request value.
183 private final SearchScope scope;
184
185 // The set of attributes to include in entries matching the join criteria.
186 private final String[] attributes;
187
188
189
190 /**
191 * Creates a new join request value with the provided information.
192 *
193 * @param joinRule The join rule for this join request value. It must
194 * not be {@code null}.
195 * @param baseDN The base DN for this join request value. It must
196 * not be {@code null}.
197 * @param scope The scope for this join request value. It may be
198 * {@code null} if the scope from the associated search
199 * request should be used.
200 * @param derefPolicy The alias dereferencing policy for this join request
201 * value. It may be {@code null} if the dereference
202 * policy from the associated search request should be
203 * used.
204 * @param sizeLimit The maximum number of entries to allow when
205 * performing the join. It may be {@code null} if the
206 * size limit from the associated search request should
207 * be used.
208 * @param filter An additional filter which must match target entries
209 * for them to be included in the join. This may be
210 * {@code null} if no additional filter is required and
211 * the join rule should be the only criteria used when
212 * performing the join.
213 * @param attributes The set of attributes that the client wishes to be
214 * included in joined entries. It may be {@code null}
215 * or empty to indicate that all user attributes should
216 * be included. It may also contain special values like
217 * "1.1" to indicate that no attributes should be
218 * included, "*" to indicate that all user attributes
219 * should be included, "+" to indicate that all
220 * operational attributes should be included, or
221 * "@ocname" to indicate that all required and optional
222 * attributes associated with the "ocname" object class
223 * should be included.
224 * @param requireMatch Indicates whether a search result entry is required
225 * to be joined with at least one entry for it to be
226 * returned to the client.
227 * @param nestedJoin A set of join criteria that should be applied to
228 * entries joined with this join request value. It may
229 * be {@code null} if no nested join is needed.
230 */
231 public JoinRequestValue(final JoinRule joinRule, final JoinBaseDN baseDN,
232 final SearchScope scope, final DereferencePolicy derefPolicy,
233 final Integer sizeLimit, final Filter filter,
234 final String[] attributes, final boolean requireMatch,
235 final JoinRequestValue nestedJoin)
236 {
237 ensureNotNull(joinRule, baseDN);
238
239 this.joinRule = joinRule;
240 this.baseDN = baseDN;
241 this.scope = scope;
242 this.derefPolicy = derefPolicy;
243 this.sizeLimit = sizeLimit;
244 this.filter = filter;
245 this.requireMatch = requireMatch;
246 this.nestedJoin = nestedJoin;
247
248 if (attributes == null)
249 {
250 this.attributes = NO_ATTRIBUTES;
251 }
252 else
253 {
254 this.attributes = attributes;
255 }
256 }
257
258
259
260 /**
261 * Retrieves the join rule for this join request value.
262 *
263 * @return The join rule for this join request value.
264 */
265 public JoinRule getJoinRule()
266 {
267 return joinRule;
268 }
269
270
271
272 /**
273 * Retrieves the join base DN for this join request value.
274 *
275 * @return The join base DN for this join request value.
276 */
277 public JoinBaseDN getBaseDN()
278 {
279 return baseDN;
280 }
281
282
283
284 /**
285 * Retrieves the scope for this join request value.
286 *
287 * @return The scope for this join request value, or {@code null} if the
288 * scope from the associated search request should be used.
289 */
290 public SearchScope getScope()
291 {
292 return scope;
293 }
294
295
296
297 /**
298 * Retrieves the alias dereferencing policy for this join request value.
299 *
300 * @return The alias dereferencing policy for this join request value, or
301 * {@code null} if the policy from the associated search request
302 * should be used.
303 */
304 public DereferencePolicy getDerefPolicy()
305 {
306 return derefPolicy;
307 }
308
309
310
311 /**
312 * Retrieves the size limit for this join request value.
313 *
314 * @return The size limit for this join request value, or {@code null} if the
315 * size limit from the associated search request should be used.
316 */
317 public Integer getSizeLimit()
318 {
319 return sizeLimit;
320 }
321
322
323
324 /**
325 * Retrieves a filter with additional criteria that must match a target entry
326 * for it to be joined with a search result entry.
327 *
328 * @return A filter with additional criteria that must match a target entry
329 * for it to be joined with a search result entry, or {@code null} if
330 * no additional filter is needed.
331 */
332 public Filter getFilter()
333 {
334 return filter;
335 }
336
337
338
339 /**
340 * Retrieves the set of requested attributes that should be included in
341 * joined entries.
342 *
343 * @return The set of requested attributes that should be included in joined
344 * entries, or an empty array if all user attributes should be
345 * requested.
346 */
347 public String[] getAttributes()
348 {
349 return attributes;
350 }
351
352
353
354 /**
355 * Indicates whether a search result entry will be required to be joined with
356 * at least one entry for that entry to be returned to the client.
357 *
358 * @return {@code true} if a search result entry must be joined with at least
359 * one other entry for it to be returned to the client, or
360 * {@code false} if a search result entry may be returned even if it
361 * is not joined with any other entries.
362 */
363 public boolean requireMatch()
364 {
365 return requireMatch;
366 }
367
368
369
370 /**
371 * Retrieves the nested join for this join request value, if defined.
372 *
373 * @return The nested join for this join request value, or {@code null} if
374 * there is no nested join for this join request value.
375 */
376 public JoinRequestValue getNestedJoin()
377 {
378 return nestedJoin;
379 }
380
381
382
383 /**
384 * Encodes this join request value as appropriate for inclusion in the join
385 * request control.
386 *
387 * @return The ASN.1 element containing the encoded join request value.
388 */
389 ASN1Element encode()
390 {
391 final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(9);
392
393 elements.add(joinRule.encode());
394 elements.add(baseDN.encode());
395
396 if (scope != null)
397 {
398 elements.add(new ASN1Enumerated(TYPE_SCOPE, scope.intValue()));
399 }
400
401 if (derefPolicy != null)
402 {
403 elements.add(new ASN1Enumerated(TYPE_DEREF_POLICY,
404 derefPolicy.intValue()));
405 }
406
407 if (sizeLimit != null)
408 {
409 elements.add(new ASN1Integer(TYPE_SIZE_LIMIT, sizeLimit));
410 }
411
412 if (filter != null)
413 {
414 elements.add(new ASN1OctetString(TYPE_FILTER, filter.encode().encode()));
415 }
416
417 if ((attributes != null) && (attributes.length > 0))
418 {
419 final ASN1Element[] attrElements = new ASN1Element[attributes.length];
420 for (int i=0; i < attributes.length; i++)
421 {
422 attrElements[i] = new ASN1OctetString(attributes[i]);
423 }
424 elements.add(new ASN1Sequence(TYPE_ATTRIBUTES, attrElements));
425 }
426
427 if (requireMatch)
428 {
429 elements.add(new ASN1Boolean(TYPE_REQUIRE_MATCH, requireMatch));
430 }
431
432 if (nestedJoin != null)
433 {
434 elements.add(new ASN1OctetString(TYPE_NESTED_JOIN,
435 nestedJoin.encode().getValue()));
436 }
437
438 return new ASN1Sequence(elements);
439 }
440
441
442
443 /**
444 * Decodes the provided ASN.1 element as a join request value.
445 *
446 * @param element The element to be decoded.
447 *
448 * @return The decoded join request value.
449 *
450 * @throws LDAPException If the provided ASN.1 element cannot be decoded as
451 * a join request value.
452 */
453 static JoinRequestValue decode(final ASN1Element element)
454 throws LDAPException
455 {
456 try
457 {
458 final ASN1Element[] elements =
459 ASN1Sequence.decodeAsSequence(element).elements();
460 final JoinRule joinRule = JoinRule.decode(elements[0]);
461 final JoinBaseDN baseDN = JoinBaseDN.decode(elements[1]);
462
463 SearchScope scope = null;
464 DereferencePolicy derefPolicy = null;
465 Integer sizeLimit = null;
466 Filter filter = null;
467 String[] attributes = NO_ATTRIBUTES;
468 boolean requireMatch = false;
469 JoinRequestValue nestedJoin = null;
470
471 for (int i=2; i < elements.length; i++)
472 {
473 switch (elements[i].getType())
474 {
475 case TYPE_SCOPE:
476 scope = SearchScope.valueOf(
477 ASN1Enumerated.decodeAsEnumerated(elements[i]).intValue());
478 break;
479
480 case TYPE_DEREF_POLICY:
481 derefPolicy = DereferencePolicy.valueOf(
482 ASN1Enumerated.decodeAsEnumerated(elements[i]).intValue());
483 break;
484
485 case TYPE_SIZE_LIMIT:
486 sizeLimit = ASN1Integer.decodeAsInteger(elements[i]).intValue();
487 break;
488
489 case TYPE_FILTER:
490 filter = Filter.decode(ASN1Element.decode(elements[i].getValue()));
491 break;
492
493 case TYPE_ATTRIBUTES:
494 final ArrayList<String> attrList = new ArrayList<String>();
495 for (final ASN1Element e :
496 ASN1Sequence.decodeAsSequence(elements[i]).elements())
497 {
498 attrList.add(
499 ASN1OctetString.decodeAsOctetString(e).stringValue());
500 }
501
502 attributes = new String[attrList.size()];
503 attrList.toArray(attributes);
504 break;
505
506 case TYPE_REQUIRE_MATCH:
507 requireMatch =
508 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue();
509 break;
510
511 case TYPE_NESTED_JOIN:
512 nestedJoin = decode(elements[i]);
513 break;
514
515 default:
516 throw new LDAPException(ResultCode.DECODING_ERROR,
517 ERR_JOIN_REQUEST_VALUE_INVALID_ELEMENT_TYPE.get(
518 elements[i].getType()));
519 }
520 }
521
522 return new JoinRequestValue(joinRule, baseDN, scope, derefPolicy,
523 sizeLimit, filter, attributes, requireMatch, nestedJoin);
524 }
525 catch (Exception e)
526 {
527 debugException(e);
528
529 throw new LDAPException(ResultCode.DECODING_ERROR,
530 ERR_JOIN_REQUEST_VALUE_CANNOT_DECODE.get(getExceptionMessage(e)), e);
531 }
532 }
533
534
535
536 /**
537 * Retrieves a string representation of this join request value.
538 *
539 * @return A string representation of this join request value.
540 */
541 @Override()
542 public String toString()
543 {
544 final StringBuilder buffer = new StringBuilder();
545 toString(buffer);
546 return buffer.toString();
547 }
548
549
550
551 /**
552 * Appends a string representation of this join request value to the provided
553 * buffer.
554 *
555 * @param buffer The buffer to which the information should be appended.
556 */
557 public void toString(final StringBuilder buffer)
558 {
559 buffer.append("JoinRequestValue(joinRule=");
560 joinRule.toString(buffer);
561 buffer.append(", baseDN=");
562 baseDN.toString(buffer);
563 buffer.append(", scope=");
564 buffer.append(String.valueOf(scope));
565 buffer.append(", derefPolicy=");
566 buffer.append(String.valueOf(derefPolicy));
567 buffer.append(", sizeLimit=");
568 buffer.append(sizeLimit);
569 buffer.append(", filter=");
570
571 if (filter == null)
572 {
573 buffer.append("null");
574 }
575 else
576 {
577 buffer.append('\'');
578 filter.toString(buffer);
579 buffer.append('\'');
580 }
581
582 buffer.append(", attributes={");
583
584 for (int i=0; i < attributes.length; i++)
585 {
586 if (i > 0)
587 {
588 buffer.append(", ");
589 }
590 buffer.append(attributes[i]);
591 }
592
593 buffer.append("}, requireMatch=");
594 buffer.append(requireMatch);
595 buffer.append(", nestedJoin=");
596
597 if (nestedJoin == null)
598 {
599 buffer.append("null");
600 }
601 else
602 {
603 nestedJoin.toString(buffer);
604 }
605
606 buffer.append(')');
607 }
608 }