001/*
002 * Copyright 2012-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2012-2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2012-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk.unboundidds.controls;
037
038
039
040import java.util.ArrayList;
041import java.util.Arrays;
042import java.util.Collection;
043import java.util.Collections;
044import java.util.EnumSet;
045import java.util.Iterator;
046import java.util.List;
047import java.util.Set;
048
049import com.unboundid.asn1.ASN1Element;
050import com.unboundid.asn1.ASN1Enumerated;
051import com.unboundid.asn1.ASN1OctetString;
052import com.unboundid.asn1.ASN1Sequence;
053import com.unboundid.ldap.sdk.Control;
054import com.unboundid.ldap.sdk.JSONControlDecodeHelper;
055import com.unboundid.ldap.sdk.LDAPException;
056import com.unboundid.ldap.sdk.ResultCode;
057import com.unboundid.util.Debug;
058import com.unboundid.util.NotMutable;
059import com.unboundid.util.NotNull;
060import com.unboundid.util.StaticUtils;
061import com.unboundid.util.ThreadSafety;
062import com.unboundid.util.ThreadSafetyLevel;
063import com.unboundid.util.Validator;
064import com.unboundid.util.json.JSONArray;
065import com.unboundid.util.json.JSONField;
066import com.unboundid.util.json.JSONObject;
067import com.unboundid.util.json.JSONString;
068import com.unboundid.util.json.JSONValue;
069
070import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
071
072
073
074/**
075 * This class provides an implementation of a control that can be used to
076 * indicate that the server should suppress the update to one or more
077 * operational attributes for the associated request.
078 * <BR>
079 * <BLOCKQUOTE>
080 *   <B>NOTE:</B>  This class, and other classes within the
081 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
082 *   supported for use against Ping Identity, UnboundID, and
083 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
084 *   for proprietary functionality or for external specifications that are not
085 *   considered stable or mature enough to be guaranteed to work in an
086 *   interoperable way with other types of LDAP servers.
087 * </BLOCKQUOTE>
088 * <BR>
089 * The request control has an OID of 1.3.6.1.4.1.30221.2.5.27, and the
090 * criticality may be either {@code true} or {@code false}.  The control must
091 * have a value with the following encoding:
092 * <PRE>
093 *   SuppressOperationalAttributeUpdateRequestValue ::= SEQUENCE {
094 *        suppressTypes     [0] SEQUENCE OF ENUMERATED {
095 *             last-access-time     (0),
096 *             last-login-time      (1),
097 *             last-login-ip        (2),
098 *             lastmod              (3),
099 *             ... },
100 *        ... }
101 * </PRE>
102 */
103@NotMutable()
104@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
105public final class SuppressOperationalAttributeUpdateRequestControl
106       extends Control
107{
108  /**
109   * The OID (1.3.6.1.4.1.30221.2.5.27) for the suppress operational attribute
110   * update request control.
111   */
112  @NotNull public static final String SUPPRESS_OP_ATTR_UPDATE_REQUEST_OID =
113       "1.3.6.1.4.1.30221.2.5.27";
114
115
116
117  /**
118   * The BER type to use for the set of suppress types.
119   */
120  private static final byte TYPE_SUPPRESS_TYPES = (byte) 0x80;
121
122
123
124  /**
125   * The name of the field used to hold the suppress types in the JSON
126   * representation of this control.
127   */
128  @NotNull private static final String JSON_FIELD_SUPPRESS_TYPES =
129       "suppress-types";
130
131
132
133  /**
134   * The last-access-time suppress type value to use in the JSON representation
135   * of this control.
136   */
137  @NotNull private static final String JSON_SUPPRESS_TYPE_LAST_ACCESS_TIME =
138       "last-access-time";
139
140
141
142  /**
143   * The last-login-ip-address suppress type value to use in the JSON
144   * representation of this control.
145   */
146  @NotNull private static final String
147       JSON_SUPPRESS_TYPE_LAST_LOGIN_IP_ADDRESS = "last-login-ip-address";
148
149
150
151  /**
152   * The last-login-time suppress type value to use in the JSON representation
153   * of this control.
154   */
155  @NotNull private static final String JSON_SUPPRESS_TYPE_LAST_LOGIN_TIME =
156       "last-login-time";
157
158
159
160  /**
161   * The lastmod suppress type value to use in the JSON representation of this
162   * control.
163   */
164  @NotNull private static final String JSON_SUPPRESS_TYPE_LASTMOD = "lastmod";
165
166
167  /**
168   * The serial version UID for this serializable class.
169   */
170  private static final long serialVersionUID = 4603958484615351672L;
171
172
173
174  // The set of suppress types to include in the control.
175  @NotNull private final Set<SuppressType> suppressTypes;
176
177
178
179  /**
180   * Creates a new instance of this control that will suppress updates to the
181   * specified kinds of operational attributes.  It will not be critical.
182   *
183   * @param  suppressTypes  The set of suppress types to include in the control.
184   *                        It must not be {@code null} or empty.
185   */
186  public SuppressOperationalAttributeUpdateRequestControl(
187              @NotNull final SuppressType... suppressTypes)
188  {
189    this(false, suppressTypes);
190  }
191
192
193
194  /**
195   * Creates a new instance of this control that will suppress updates to the
196   * specified kinds of operational attributes.  It will not be critical.
197   *
198   * @param  suppressTypes  The set of suppress types to include in the control.
199   *                        It must not be {@code null} or empty.
200   */
201  public SuppressOperationalAttributeUpdateRequestControl(
202              @NotNull final Collection<SuppressType> suppressTypes)
203  {
204    this(false, suppressTypes);
205  }
206
207
208
209  /**
210   * Creates a new instance of this control that will suppress updates to the
211   * specified kinds of operational attributes.
212   *
213   * @param  isCritical     Indicates whether the control should be considered
214   *                        critical.
215   * @param  suppressTypes  The set of suppress types to include in the control.
216   *                        It must not be {@code null} or empty.
217   */
218  public SuppressOperationalAttributeUpdateRequestControl(
219              final boolean isCritical,
220              @NotNull final SuppressType... suppressTypes)
221  {
222    this(isCritical, Arrays.asList(suppressTypes));
223  }
224
225
226
227  /**
228   * Creates a new instance of this control that will suppress updates to the
229   * specified kinds of operational attributes.
230   *
231   * @param  isCritical     Indicates whether the control should be considered
232   *                        critical.
233   * @param  suppressTypes  The set of suppress types to include in the control.
234   *                        It must not be {@code null} or empty.
235   */
236  public SuppressOperationalAttributeUpdateRequestControl(
237              final boolean isCritical,
238              @NotNull final Collection<SuppressType> suppressTypes)
239  {
240    super(SUPPRESS_OP_ATTR_UPDATE_REQUEST_OID, isCritical,
241         encodeValue(suppressTypes));
242
243    Validator.ensureFalse(suppressTypes.isEmpty());
244
245    final EnumSet<SuppressType> s = EnumSet.noneOf(SuppressType.class);
246    for (final SuppressType t : suppressTypes)
247    {
248      s.add(t);
249    }
250
251    this.suppressTypes = Collections.unmodifiableSet(s);
252  }
253
254
255
256  /**
257   * Decodes the provided generic control as a suppress operational attribute
258   * update request control.
259   *
260   * @param  control  The generic control to be decoded as a suppress
261   *                  operational attribute update request control.
262   *
263   * @throws  LDAPException  If a problem is encountered while attempting to
264   *                         decode the provided control.
265   */
266  public SuppressOperationalAttributeUpdateRequestControl(
267              @NotNull final Control control)
268         throws LDAPException
269  {
270    super(control);
271
272    final ASN1OctetString value = control.getValue();
273    if (value == null)
274    {
275      throw new LDAPException(ResultCode.DECODING_ERROR,
276           ERR_SUPPRESS_OP_ATTR_UPDATE_REQUEST_MISSING_VALUE.get());
277    }
278
279    try
280    {
281      final ASN1Sequence valueSequence =
282           ASN1Sequence.decodeAsSequence(value.getValue());
283      final ASN1Sequence suppressTypesSequence =
284           ASN1Sequence.decodeAsSequence(valueSequence.elements()[0]);
285
286      final EnumSet<SuppressType> s = EnumSet.noneOf(SuppressType.class);
287      for (final ASN1Element e : suppressTypesSequence.elements())
288      {
289        final ASN1Enumerated ae = ASN1Enumerated.decodeAsEnumerated(e);
290        final SuppressType t = SuppressType.valueOf(ae.intValue());
291        if (t == null)
292        {
293          throw new LDAPException(ResultCode.DECODING_ERROR,
294               ERR_SUPPRESS_OP_ATTR_UNRECOGNIZED_SUPPRESS_TYPE.get(
295                    ae.intValue()));
296        }
297        else
298        {
299          s.add(t);
300        }
301      }
302
303      suppressTypes = Collections.unmodifiableSet(s);
304    }
305    catch (final LDAPException le)
306    {
307      Debug.debugException(le);
308      throw le;
309    }
310    catch (final Exception e)
311    {
312      Debug.debugException(e);
313      throw new LDAPException(ResultCode.DECODING_ERROR,
314           ERR_SUPPRESS_OP_ATTR_UPDATE_REQUEST_CANNOT_DECODE.get(
315                StaticUtils.getExceptionMessage(e)),
316           e);
317    }
318  }
319
320
321
322  /**
323   * Encodes the provided information into an octet string suitable for use as
324   * the value of this control.
325   *
326   * @param  suppressTypes  The set of suppress types to include in the control.
327   *                        It must not be {@code null} or empty.
328   *
329   * @return  The ASN.1 octet string containing the encoded value.
330   */
331  @NotNull()
332  private static ASN1OctetString encodeValue(
333                      @NotNull final Collection<SuppressType> suppressTypes)
334  {
335    final ArrayList<ASN1Element> suppressTypeElements =
336         new ArrayList<>(suppressTypes.size());
337    for (final SuppressType t : suppressTypes)
338    {
339      suppressTypeElements.add(new ASN1Enumerated(t.intValue()));
340    }
341
342    final ASN1Sequence valueSequence = new ASN1Sequence(
343         new ASN1Sequence(TYPE_SUPPRESS_TYPES, suppressTypeElements));
344    return new ASN1OctetString(valueSequence.encode());
345  }
346
347
348
349  /**
350   * Retrieves the set of suppress types for this control.
351   *
352   * @return  The set of suppress types for this control.
353   */
354  @NotNull()
355  public Set<SuppressType> getSuppressTypes()
356  {
357    return suppressTypes;
358  }
359
360
361
362  /**
363   * {@inheritDoc}
364   */
365  @Override()
366  @NotNull()
367  public String getControlName()
368  {
369    return INFO_CONTROL_NAME_SUPPRESS_OP_ATTR_UPDATE_REQUEST.get();
370  }
371
372
373
374  /**
375   * Retrieves a representation of this suppress operational attribute update
376   * request control as a JSON object.  The JSON object uses the following
377   * fields:
378   * <UL>
379   *   <LI>
380   *     {@code oid} -- A mandatory string field whose value is the object
381   *     identifier for this control.  For the suppress operational attribute
382   *     update request control, the OID is "1.3.6.1.4.1.30221.2.5.27".
383   *   </LI>
384   *   <LI>
385   *     {@code control-name} -- An optional string field whose value is a
386   *     human-readable name for this control.  This field is only intended for
387   *     descriptive purposes, and when decoding a control, the {@code oid}
388   *     field should be used to identify the type of control.
389   *   </LI>
390   *   <LI>
391   *     {@code criticality} -- A mandatory Boolean field used to indicate
392   *     whether this control is considered critical.
393   *   </LI>
394   *   <LI>
395   *     {@code value-base64} -- An optional string field whose value is a
396   *     base64-encoded representation of the raw value for this suppress
397   *     operational attribute update request control.  Exactly one of the
398   *     {@code value-base64} and {@code value-json} fields must be present.
399   *   </LI>
400   *   <LI>
401   *     {@code value-json} -- An optional JSON object field whose value is a
402   *     user-friendly representation of the value for this suppress operational
403   *     attribute update control.  Exactly one of the {@code value-base64} and
404   *     {@code value-json} fields must be present, and if the
405   *     {@code value-json} field is used, then it will use the following
406   *     fields:
407   *     <UL>
408   *       <LI>
409   *         {@code suppress-types} -- A mandatory array field whose values are
410   *         the names of the types of updates that should be suppressed.
411   *         Allowed values include "{@code last-access-time}",
412   *         "{@code last-login-time}", "{@code last-login-ip-address}",
413   *         and "{@code lastmod}".
414   *       </LI>
415   *     </UL>
416   *   </LI>
417   * </UL>
418   *
419   * @return  A JSON object that contains a representation of this control.
420   */
421  @Override()
422  @NotNull()
423  public JSONObject toJSONControl()
424  {
425    final List<JSONValue> suppressTypeValues =
426         new ArrayList<>(suppressTypes.size());
427    for (final SuppressType suppressType : suppressTypes)
428    {
429      switch (suppressType)
430      {
431        case LAST_ACCESS_TIME:
432          suppressTypeValues.add(new JSONString(
433               JSON_SUPPRESS_TYPE_LAST_ACCESS_TIME));
434          break;
435        case LAST_LOGIN_TIME:
436          suppressTypeValues.add(new JSONString(
437               JSON_SUPPRESS_TYPE_LAST_LOGIN_TIME));
438          break;
439        case LAST_LOGIN_IP:
440          suppressTypeValues.add(new JSONString(
441               JSON_SUPPRESS_TYPE_LAST_LOGIN_IP_ADDRESS));
442          break;
443        case LASTMOD:
444          suppressTypeValues.add(new JSONString(JSON_SUPPRESS_TYPE_LASTMOD));
445          break;
446      }
447    }
448
449    return new JSONObject(
450         new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID,
451              SUPPRESS_OP_ATTR_UPDATE_REQUEST_OID),
452         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME,
453              INFO_CONTROL_NAME_SUPPRESS_OP_ATTR_UPDATE_REQUEST.get()),
454         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY,
455              isCritical()),
456         new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON,
457              new JSONObject(
458                   new JSONField(JSON_FIELD_SUPPRESS_TYPES,
459                        new JSONArray(suppressTypeValues)))));
460  }
461
462
463
464  /**
465   * Attempts to decode the provided object as a JSON representation of a
466   * suppress operational attribute update request control.
467   *
468   * @param  controlObject  The JSON object to be decoded.  It must not be
469   *                        {@code null}.
470   * @param  strict         Indicates whether to use strict mode when decoding
471   *                        the provided JSON object.  If this is {@code true},
472   *                        then this method will throw an exception if the
473   *                        provided JSON object contains any unrecognized
474   *                        fields.  If this is {@code false}, then unrecognized
475   *                        fields will be ignored.
476   *
477   * @return  The suppress operational attribute update request control that was
478   *          decoded from the provided JSON object.
479   *
480   * @throws  LDAPException  If the provided JSON object cannot be parsed as a
481   *                         valid suppress operational attribute update request
482   *                         control.
483   */
484  @NotNull()
485  public static SuppressOperationalAttributeUpdateRequestControl
486              decodeJSONControl(@NotNull final JSONObject controlObject,
487                                final boolean strict)
488         throws LDAPException
489  {
490    final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper(
491         controlObject, strict, true, true);
492
493    final ASN1OctetString rawvalue = jsonControl.getRawValue();
494    if (rawvalue != null)
495    {
496      return new SuppressOperationalAttributeUpdateRequestControl(new Control(
497           jsonControl.getOID(), jsonControl.getCriticality(), rawvalue));
498    }
499
500
501    final JSONObject valueObject = jsonControl.getValueObject();
502
503    final List<JSONValue> suppressTypeValues =
504         valueObject.getFieldAsArray(JSON_FIELD_SUPPRESS_TYPES);
505    if (suppressTypeValues == null)
506    {
507      throw new LDAPException(ResultCode.DECODING_ERROR,
508           ERR_SUPPRESS_OP_ATTR_UPDATE_REQUEST_JSON_MISSING_SUPPRESS_TYPES.get(
509                controlObject.toSingleLineString(),
510                JSON_FIELD_SUPPRESS_TYPES));
511    }
512
513    if (suppressTypeValues.isEmpty())
514    {
515      throw new LDAPException(ResultCode.DECODING_ERROR,
516           ERR_SUPPRESS_OP_ATTR_UPDATE_REQUEST_JSON_EMPTY_SUPPRESS_TYPES.get(
517                controlObject.toSingleLineString(),
518                JSON_FIELD_SUPPRESS_TYPES));
519    }
520
521    final Set<SuppressType> suppressTypes = EnumSet.noneOf(SuppressType.class);
522    for (final JSONValue suppressTypeValue : suppressTypeValues)
523    {
524      if (suppressTypeValue instanceof JSONString)
525      {
526        final String suppressTypeString =
527             ((JSONString) suppressTypeValue).stringValue();
528        switch (suppressTypeString)
529        {
530          case JSON_SUPPRESS_TYPE_LAST_ACCESS_TIME:
531            suppressTypes.add(SuppressType.LAST_ACCESS_TIME);
532            break;
533          case JSON_SUPPRESS_TYPE_LAST_LOGIN_TIME:
534            suppressTypes.add(SuppressType.LAST_LOGIN_TIME);
535            break;
536          case JSON_SUPPRESS_TYPE_LAST_LOGIN_IP_ADDRESS:
537            suppressTypes.add(SuppressType.LAST_LOGIN_IP);
538            break;
539          case JSON_SUPPRESS_TYPE_LASTMOD:
540            suppressTypes.add(SuppressType.LASTMOD);
541            break;
542          default:
543            throw new LDAPException(ResultCode.DECODING_ERROR,
544                 ERR_SUPPRESS_OP_ATTR_UPDATE_REQUEST_JSON_UNKNOWN_SUPPRESS_TYPE.
545                      get(controlObject.toSingleLineString(),
546                           JSON_FIELD_SUPPRESS_TYPES,
547                           JSON_SUPPRESS_TYPE_LAST_ACCESS_TIME,
548                           JSON_SUPPRESS_TYPE_LAST_LOGIN_TIME,
549                           JSON_SUPPRESS_TYPE_LAST_LOGIN_IP_ADDRESS,
550                           JSON_SUPPRESS_TYPE_LASTMOD));
551        }
552      }
553      else
554      {
555        throw new LDAPException(ResultCode.DECODING_ERROR,
556             ERR_SUPPRESS_OP_ATTR_UPDATE_REQUEST_JSON_SUPPRESS_TYPE_NOT_STRING.
557                  get(controlObject.toSingleLineString(),
558                       JSON_FIELD_SUPPRESS_TYPES));
559      }
560    }
561
562
563    if (strict)
564    {
565      final List<String> unrecognizedFields =
566           JSONControlDecodeHelper.getControlObjectUnexpectedFields(
567                valueObject, JSON_FIELD_SUPPRESS_TYPES);
568      if (! unrecognizedFields.isEmpty())
569      {
570        throw new LDAPException(ResultCode.DECODING_ERROR,
571             ERR_SUPPRESS_OP_ATTR_UPDATE_REQUEST_JSON_UNRECOGNIZED_FIELD.get(
572                  controlObject.toSingleLineString(),
573                  unrecognizedFields.get(0)));
574      }
575    }
576
577
578    return new SuppressOperationalAttributeUpdateRequestControl(
579         jsonControl.getCriticality(), suppressTypes);
580  }
581
582
583
584  /**
585   * {@inheritDoc}
586   */
587  @Override()
588  public void toString(@NotNull final StringBuilder buffer)
589  {
590    buffer.append("SuppressOperationalAttributeUpdateRequestControl(" +
591         "isCritical=");
592    buffer.append(isCritical());
593    buffer.append(", suppressTypes={");
594
595    final Iterator<SuppressType> iterator = suppressTypes.iterator();
596    while (iterator.hasNext())
597    {
598      buffer.append(iterator.next().name());
599      if (iterator.hasNext())
600      {
601        buffer.append(',');
602      }
603    }
604
605    buffer.append("})");
606  }
607}