001/* 002 * Copyright 2025 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2025 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) 2025 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.forgerockds.controls; 037 038 039 040import java.util.ArrayList; 041import java.util.LinkedHashMap; 042import java.util.List; 043import java.util.Map; 044 045import com.unboundid.asn1.ASN1Element; 046import com.unboundid.asn1.ASN1OctetString; 047import com.unboundid.asn1.ASN1Sequence; 048import com.unboundid.ldap.sdk.Control; 049import com.unboundid.ldap.sdk.JSONControlDecodeHelper; 050import com.unboundid.ldap.sdk.LDAPException; 051import com.unboundid.ldap.sdk.ResultCode; 052import com.unboundid.util.Debug; 053import com.unboundid.util.NotMutable; 054import com.unboundid.util.NotNull; 055import com.unboundid.util.Nullable; 056import com.unboundid.util.StaticUtils; 057import com.unboundid.util.ThreadSafety; 058import com.unboundid.util.ThreadSafetyLevel; 059import com.unboundid.util.Validator; 060import com.unboundid.util.json.JSONField; 061import com.unboundid.util.json.JSONObject; 062import com.unboundid.util.json.JSONString; 063import com.unboundid.util.json.JSONValue; 064 065import static com.unboundid.ldap.sdk.forgerockds.controls.ControlMessages.*; 066 067 068 069/** 070 * This class provides support for an implementation of a request control that 071 * may be used to convey a 072 * <a href="https://www.w3.org/TR/trace-context/">W3C trace context</a> to the 073 * Directory Server in support of distributed tracing. 074 * <BR> 075 * This control is based on an implementation originally created for use in the 076 * ForgeRock OpenDJ Directory Server, now known as PingDS. It may be included 077 * in any kind of request. It has an OID of 1.3.6.1.4.1.36733.2.1.5.7, and it 078 * must have a value with the following encoding: 079 * <PRE> 080 * W3CTraceContextRequestValue ::= SEQUENCE { 081 * traceParent OCTET STRING, 082 * traceState OCTET STRING OPTIONAL 083 * } 084 * </PRE> 085 */ 086@NotMutable() 087@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 088public final class W3CTraceContextRequestControl 089 extends Control 090{ 091 /** 092 * The OID (1.3.6.1.4.1.36733.2.1.5.7) for the W3C trace context request 093 * control. 094 */ 095 @NotNull public static final String W3C_TRACE_CONTEXT_REQUEST_OID = 096 "1.3.6.1.4.1.36733.2.1.5.7"; 097 098 099 100 /** 101 * The name of the field used to hold the "traceparent" header value in the 102 * JSON representation of this control. 103 */ 104 @NotNull private static final String JSON_FIELD_TRACE_PARENT = "trace-parent"; 105 106 107 108 /** 109 * The name of the field used to hold the "tracestate" header value in the 110 * JSON representation of this control. 111 */ 112 @NotNull private static final String JSON_FIELD_TRACE_STATE = "trace-state"; 113 114 115 116 /** 117 * The serial version UID for this serializable class. 118 */ 119 private static final long serialVersionUID = -4086202983109938297L; 120 121 122 123 // The value of the "traceparent" header for this control. 124 @NotNull private final String traceParent; 125 126 // The value of the "tracestate" header for this control. 127 @Nullable private final String traceState; 128 129 130 131 /** 132 * Creates a new instance of this W3C trace context request control with the 133 * provided information. It will not be considered critical. 134 * 135 * @param traceParent The value of the "traceparent" header, as defined in 136 * the W3C specification. It must not be {@code null} 137 * or empty. 138 * @param traceState The value of the "tracestate" header. It may be 139 * {@code null} if no additional vendor-specific state 140 * information is needed. 141 */ 142 public W3CTraceContextRequestControl(@NotNull final String traceParent, 143 @Nullable final String traceState) 144 { 145 this(traceParent, traceState, false); 146 } 147 148 149 150 /** 151 * Creates a new instance of this W3C trace context request control with the 152 * provided information. It will not be considered critical. 153 * 154 * @param traceParent The value of the "traceparent" header, as defined in 155 * the W3C specification. It must not be {@code null} 156 * or empty. 157 * @param traceState The value of the "tracestate" header. It may be 158 * {@code null} if no additional vendor-specific state 159 * information is needed. 160 * @param isCritical Indicates whether this control should be considered 161 * critical. 162 */ 163 public W3CTraceContextRequestControl(@NotNull final String traceParent, 164 @Nullable final String traceState, 165 final boolean isCritical) 166 { 167 super(W3C_TRACE_CONTEXT_REQUEST_OID, isCritical, 168 encodeValue(traceParent, traceState)); 169 170 this.traceParent = traceParent; 171 this.traceState = traceState; 172 } 173 174 175 176 /** 177 * Encodes the provided information into an ASN.1 octet string suitable for 178 * use as the value of a W3C trace context request control. 179 * 180 * @param traceParent The value of the "traceparent" header, as defined in 181 * the W3C specification. It must not be {@code null} 182 * or empty. 183 * @param traceState The value of the "tracestate" header. It may be 184 * {@code null} if no additional vendor-specific state 185 * information is needed. 186 * 187 * @return The encoded value for the request control. 188 */ 189 @NotNull() 190 private static ASN1OctetString encodeValue(@NotNull final String traceParent, 191 @Nullable final String traceState) 192 { 193 Validator.ensureNotNullWithMessage(traceParent, 194 "W3CTraceContextRequestControl.traceParent must not be null"); 195 196 final List<ASN1Element> valueElements = new ArrayList<>(2); 197 valueElements.add(new ASN1OctetString(traceParent)); 198 199 if (traceState != null) 200 { 201 valueElements.add(new ASN1OctetString(traceState)); 202 } 203 204 final ASN1Sequence valueSequence = new ASN1Sequence(valueElements); 205 return new ASN1OctetString(valueSequence.encode()); 206 } 207 208 209 210 /** 211 * Creates a new W3C trace context request control that has been decoded from 212 * the provided generic control. 213 * 214 * @param control The generic control to decode as a W3C trace context 215 * request control. It must not be {@code null}. 216 * 217 * @throws LDAPException If the provided generic control cannot be decoded 218 * as a valid W3C trace context request control. 219 */ 220 public W3CTraceContextRequestControl(@NotNull final Control control) 221 throws LDAPException 222 { 223 super(control); 224 225 226 // Ensure that the control has a value. 227 final ASN1OctetString value = control.getValue(); 228 if (value == null) 229 { 230 throw new LDAPException(ResultCode.DECODING_ERROR, 231 ERR_WC3_TRACE_CONTEXT_REQUST_NO_VALUE.get()); 232 } 233 234 235 // Parse the control value. 236 try 237 { 238 final ASN1Sequence valueSequence = 239 ASN1Sequence.decodeAsSequence(value.getValue()); 240 final ASN1Element[] valueElements = valueSequence.elements(); 241 traceParent = valueElements[0].decodeAsOctetString().stringValue(); 242 243 if (valueElements.length > 1) 244 { 245 traceState = valueElements[1].decodeAsOctetString().stringValue(); 246 } 247 else 248 { 249 traceState = null; 250 } 251 } 252 catch (final Exception e) 253 { 254 Debug.debugException(e); 255 256 throw new LDAPException(ResultCode.DECODING_ERROR, 257 ERR_WC3_TRACE_CONTEXT_REQUST_CANNOT_DECODE.get( 258 StaticUtils.getExceptionMessage(e)), 259 e); 260 } 261 } 262 263 264 265 /** 266 * Retrieves the value of the "traceparent" header for this control. 267 * 268 * @return The value of the "traceparent" header for this control. 269 */ 270 @NotNull() 271 public String getTraceParent() 272 { 273 return traceParent; 274 } 275 276 277 278 /** 279 * Retrieves the value of the "tracestate" header for this control, if 280 * provided. 281 * 282 * @return The value of the "tracestate" header for this control, or 283 * {@code null} if no such value was provided. 284 */ 285 @NotNull() 286 public String getTraceState() 287 { 288 return traceState; 289 } 290 291 292 293 /** 294 * {@inheritDoc} 295 */ 296 @Override() 297 @NotNull() 298 public String getControlName() 299 { 300 return INFO_CONTROL_NAME_WC3_TRACE_CONTEXT_REQUEST.get(); 301 } 302 303 304 305 /** 306 * Retrieves a representation of this W3C trace context request control as a 307 * JSON object. The JSON object uses the following fields: 308 * <UL> 309 * <LI> 310 * {@code oid} -- A mandatory string field whose value is the object 311 * identifier for this control. For the W3C trace context request 312 * control, the OID is "1.3.6.1.4.1.36733.2.1.5.7". 313 * </LI> 314 * <LI> 315 * {@code control-name} -- An optional string field whose value is a 316 * human-readable name for this control. This field is only intended for 317 * descriptive purposes, and when decoding a control, the {@code oid} 318 * field should be used to identify the type of control. 319 * </LI> 320 * <LI> 321 * {@code criticality} -- A mandatory Boolean field used to indicate 322 * whether this control is considered critical. 323 * </LI> 324 * <LI> 325 * {@code value-base64} -- An optional string field whose value is a 326 * base64-encoded representation of the raw value for this W3C trace 327 * context request control. Exactly one of the {@code value-base64} and 328 * {@code value-json} fields must be present. 329 * </LI> 330 * <LI> 331 * {@code value-json} -- An optional JSON object field whose value is a 332 * user-friendly representation of the value for this uniqueness request 333 * control. Exactly one of the {@code value-base64} and 334 * {@code value-json} fields must be present, and if the 335 * {@code value-json} field is used, then it will use the following 336 * fields: 337 * <UL> 338 * <LI> 339 * {@code trace-parent} -- A mandatory string field that holds the 340 * value of the "traceparent" header for this control. 341 * </LI> 342 * <LI> 343 * {@code trace-state} -- An optional string field that holds the 344 * value of the "tracestate" header for this control. 345 * </LI> 346 * </UL> 347 * </LI> 348 * </UL> 349 * 350 * @return A JSON object that contains a representation of this control. 351 */ 352 @Override() 353 @NotNull() 354 public JSONObject toJSONControl() 355 { 356 final Map<String,JSONValue> valueFields = new LinkedHashMap<>(); 357 valueFields.put(JSON_FIELD_TRACE_PARENT, new JSONString(traceParent)); 358 359 if (traceState != null) 360 { 361 valueFields.put(JSON_FIELD_TRACE_STATE, new JSONString(traceState)); 362 } 363 364 365 return new JSONObject( 366 new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID, 367 W3C_TRACE_CONTEXT_REQUEST_OID), 368 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME, 369 INFO_CONTROL_NAME_WC3_TRACE_CONTEXT_REQUEST.get()), 370 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY, 371 isCritical()), 372 new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON, 373 new JSONObject(valueFields))); 374 } 375 376 377 378 /** 379 * Attempts to decode the provided object as a JSON representation of a W3C 380 * trace context request control. 381 * 382 * @param controlObject The JSON object to be decoded. It must not be 383 * {@code null}. 384 * @param strict Indicates whether to use strict mode when decoding 385 * the provided JSON object. If this is {@code true}, 386 * then this method will throw an exception if the 387 * provided JSON object contains any unrecognized 388 * fields. If this is {@code false}, then unrecognized 389 * fields will be ignored. 390 * 391 * @return The W3C trace cotnext request control that was decoded from the 392 * provided JSON object. 393 * 394 * @throws LDAPException If the provided JSON object cannot be parsed as a 395 * valid W3C trace context request control. 396 */ 397 @NotNull() 398 public static W3CTraceContextRequestControl decodeJSONControl( 399 @NotNull final JSONObject controlObject, 400 final boolean strict) 401 throws LDAPException 402 { 403 final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper( 404 controlObject, strict, true, true); 405 406 final ASN1OctetString rawValue = jsonControl.getRawValue(); 407 if (rawValue != null) 408 { 409 return new W3CTraceContextRequestControl(new Control(jsonControl.getOID(), 410 jsonControl.getCriticality(), rawValue)); 411 } 412 413 414 final JSONObject valueObject = jsonControl.getValueObject(); 415 416 final String traceParent = 417 valueObject.getFieldAsString(JSON_FIELD_TRACE_PARENT); 418 if (traceParent == null) 419 { 420 throw new LDAPException(ResultCode.DECODING_ERROR, 421 ERR_WC3_TRACE_CONTEXT_REQUEST_MISSING_JSON_TRACE_PARENT.get( 422 JSON_FIELD_TRACE_PARENT)); 423 } 424 425 final String traceState = 426 valueObject.getFieldAsString(JSON_FIELD_TRACE_STATE); 427 428 429 if (strict) 430 { 431 final List<String> unrecognizedFields = 432 JSONControlDecodeHelper.getControlObjectUnexpectedFields( 433 valueObject, JSON_FIELD_TRACE_PARENT, JSON_FIELD_TRACE_STATE); 434 if (! unrecognizedFields.isEmpty()) 435 { 436 throw new LDAPException(ResultCode.DECODING_ERROR, 437 ERR_WC3_TRACE_CONTEXT_REQUEST_UNRECOGNIZED_JSON_FIELD.get( 438 controlObject.toSingleLineString(), 439 unrecognizedFields.get(0))); 440 } 441 } 442 443 return new W3CTraceContextRequestControl(traceParent, traceState, 444 jsonControl.getCriticality()); 445 } 446 447 448 449 /** 450 * {@inheritDoc} 451 */ 452 @Override() 453 public void toString(@NotNull final StringBuilder buffer) 454 { 455 buffer.append("W3CTraceContextRequestControl(isCritical="); 456 buffer.append(isCritical()); 457 buffer.append(", traceParent='"); 458 buffer.append(traceParent); 459 buffer.append('\''); 460 461 if (traceState != null) 462 { 463 buffer.append(", traceState='"); 464 buffer.append(traceState); 465 buffer.append('\''); 466 } 467 468 buffer.append(')'); 469 } 470}