Commit 6074743
feat(servicebus): add listSessions to session receiver clients (#48956)
* feat(servicebus): add listSessions to session receiver clients
- Add OPERATION_GET_MESSAGE_SESSIONS and related constants
- Add getMessageSessions to ServiceBusManagementNode interface
- Implement AMQP request/response in ManagementChannel with year-cap and 404 handling
- Add listSessions() overloads on ServiceBusSessionReceiverAsyncClient with Flux pagination
- Add sync wrappers on ServiceBusSessionReceiverClient
- Add unit tests for request, response, 204, 404, year-cap, and active-sessions mode
* fix: simplify pagination with takeWhile + map for skip tracking
* fix(servicebus): address review feedback on listSessions
- ManagementChannel: validate lastUpdatedTime is non-null and cap year-9999 sentinel to UTC so the wire timestamp matches DateTime.MaxValue regardless of source offset.
- ServiceBusManagementNode: drop fully-qualified types in signature; document the UTC sentinel in JavaDoc.
- ServiceBusSessionReceiverAsyncClient: use fluxError(LOGGER,...) for null updatedAfter, drop unused imports.
- ManagementChannelTests: set the SESSION_NOT_FOUND error-condition so the 404 test exercises the actual sendWithVerify pass-through path; remove unused List import.
- Apply spotless formatting.
* fix(servicebus): align listSessions with Track 1 semantics + use PagedFlux
Round 2 review feedback. Aligns with Track 1's SessionBrowser/MiscRequestResponseOperationHandler so the implementation tracks proven service behaviour, and adopts PagedFlux/PagedIterable per Azure SDK guidelines for paginated collection APIs.
Service-side alignment:
- Active-messages sentinel now matches Track 1's SessionBrowser.MAXDATE (new Date(253402300800000L), 10000-01-01T00:00:00Z UTC). The wire value is the one the broker has been validated against for years; using 9999-12-31T23:59:59.999Z (1 ms earlier) risked the broker not switching into active-messages mode.
- Pagination cursor now uses the server-returned skip plus the last session ID of the previous page, matching Track 1. Track 2 previously incremented skip locally and always passed lastSessionId=null, which can skip or duplicate sessions when the broker filters expired entries between pages.
Surface change:
- ServiceBusManagementNode.getMessageSessions now returns Mono<MessageSessionsResult> (sessionIds + nextSkip) so callers can thread the cursor.
- listSessions / listSessions(OffsetDateTime) now return PagedFlux<String> (async) and PagedIterable<String> (sync), matching the Azure SDK conventions enforced by the ServiceClient checkstyle rule.
- Renamed ManagementConstants.SESSIONS_IDS to SESSION_IDS (wire value 'sessions-ids' unchanged) so the constant reads consistently with SESSION_ID and SEQUENCE_NUMBERS.
Tests:
- Updated assertions to MessageSessionsResult and the Track 1 sentinel value (253402300800000L).
- Added getMessageSessionsHonoursServerReturnedSkip covering the case where the server returns a skip greater than requestSkip + page.size().
* fix(servicebus): validate listSessions continuation tokens + spelling
Round 4 review feedback.
- listSessionsInternal now wraps continuation-token parsing so a caller passing an invalid or opaque token via PagedFlux.byPage(String) sees a clear monoError(LOGGER, IllegalArgumentException) instead of NumberFormatException / StringIndexOutOfBoundsException.
- Renamed getMessageSessionsHonoursServerReturnedSkip to getMessageSessionsHonorsServerReturnedSkip to match the American-English spelling used in the rest of the module.
* fix(servicebus): harden listSessions response parsing + cursor encoding
Round 5 review feedback.
- ManagementChannel.getMessageSessions now defensively guards the AmqpValue body so a malformed broker/proxy response (null body, non-AmqpValue, non-Map value) returns an empty page instead of NPE/CCE breaking pagination.
- readResponseSkip now takes the parsed page size and falls back to requestSkip + pageSize when the server omits the skip field, so a malformed response advances the cursor instead of stalling on the same value.
- listSessionsInternal cursor encoding switched to <decimal-skip>|<base64url-utf8(lastSessionId)>. The URL-safe Base64 alphabet does not contain '|', so any byte sequence in the session ID round-trips without escaping. Decode failures now also surface through monoError(LOGGER, IllegalArgumentException) with descriptive messages (no embedded control characters).
- ACTIVE_MESSAGES_SENTINEL moved to ManagementConstants so the implementation and the public client reference a single source of truth for the broker-contract sentinel.
* test(servicebus): cover listSessions cursor + sync delegation paths
Round 6 review feedback: add unit coverage for the new listSessions APIs.
ServiceBusSessionReceiverAsyncClientTest:
- listSessionsActiveModeStreamsAllPagesUntilEmpty: verifies the no-arg overload uses ManagementConstants.ACTIVE_MESSAGES_SENTINEL and walks every page until the broker returns an empty page.
- listSessionsHonorsServerSkipAndLastSessionId: verifies the cursor uses the server-returned skip and lastSessionId of the previous page (Track 1 SessionBrowser semantics, even when the broker filters entries between pages).
- listSessionsRejectsNullUpdatedAfter: verifies null updatedAfter surfaces as NullPointerException on subscription.
- listSessionsRoundTripsArbitrarySessionIdsThroughCursor: verifies a session ID containing the cursor separator '|' round-trips correctly thanks to base64url encoding.
- listSessionsRejectsInvalidContinuationToken: verifies PagedFlux.byPage(invalid) surfaces IllegalArgumentException.
ServiceBusSessionReceiverClientTest:
- listSessionsDelegatesToAsync, listSessionsWithUpdatedAfterDelegatesToAsync, listSessionsPropagatesAsyncError: verify the sync overloads delegate to the async client, thread the timestamp through, and surface async errors during iteration.
* fix(servicebus): drop unreachable null check in listSessions cursor lambda
Round 7 review feedback. PagedFlux only invokes the next-page function when the previous PagedResponse carried a non-null continuation token (byPage(null) is routed to the first-page supplier instead), so the lambda never receives a null token. Removed the dead branch and added a comment explaining the contract.
* fix(servicebus): tighten listSessions argument + cursor validation
Round 8 review feedback.
- ServiceBusSessionReceiverClient.listSessions(OffsetDateTime) now validates
updatedAfter via Objects.requireNonNull eagerly (matching the JavaDoc), so the
NullPointerException surfaces on the call instead of being deferred until the
PagedIterable is iterated. New test listSessionsRejectsNullUpdatedAfterEagerly
covers this.
- listSessionsInternal now rejects continuation tokens whose decoded skip is
negative with a logged IllegalArgumentException, so byPage("-1|...") fails
fast instead of sending an invalid management request. New test
listSessionsRejectsNegativeSkipInContinuationToken covers this.
- MessageSessionsResult JavaDoc now explicitly notes it is internal (the public
modifier is required only because ServiceBusManagementNode.getMessageSessions
is consumed from a different package). The implementation/ package signals
internal status in this repo by convention.
* fix(servicebus): validate listSessions paging args + harden response skip
Round 9 review feedback.
- ManagementChannel.getMessageSessions now rejects negative skip and
non-positive top with monoError(logger, IllegalArgumentException) before
building the management request, so an invalid call fails fast instead of
producing a confusing broker error. Added test
getMessageSessionsRejectsInvalidPagingArgs covering skip=-1, top=0, top=-10.
- readResponseSkip now reads the server-returned skip as a long, requires
0 <= responseSkip <= Integer.MAX_VALUE, and falls back to a saturating
requestSkip + pageSize otherwise. This protects against a negative or
out-of-range broker value wrapping the cursor into a negative int. Extracted
the fallback into computeFallbackSkip so the saturation logic is shared and
individually testable. Added test getMessageSessionsFallsBackOnInvalidServerSkip
covering the negative-skip case.
* fix(servicebus): preserve empty-string lastSessionId cursor
Round 10 review feedback.
- ManagementChannel.getMessageSessions now uses `lastSessionId != null` to decide
whether to forward the `last-session-id` cursor, instead of
`!CoreUtils.isNullOrEmpty(...)` which collapsed both null and "". Service Bus
permits an empty session ID, so an empty-string cursor must round-trip the
same way any other ID does. Otherwise pagination would silently restart from
the first page when the previous page ended on an empty-ID session. Added
test getMessageSessionsForwardsEmptyLastSessionIdAsCursor verifying the empty
string is sent through to the broker rather than being omitted.
* fix(servicebus): accept Iterable session-IDs payload + tighten fallback comment
Round 11 review feedback.
- ManagementChannel.getMessageSessions response parsing now also accepts
Iterable<?> (List, Set, ...) in addition to Object[] for the sessions-ids
payload. Previously, if a future broker or codec change surfaced the payload
as a List the code would silently produce an empty page and prematurely
terminate pagination. Added test getMessageSessionsParsesIterableSessionIds
feeding a List as the payload and asserting both items + cursor round-trip.
- computeFallbackSkip JavaDoc tightened: requestSkip is validated as
non-negative at the call site and the addition runs in long, so the only
thing to guard against is overflow past Integer.MAX_VALUE. Dropped the
"wraps negative" branch (and its now-impossible <= 0 clamp) since it could
never trigger.
* fix(servicebus): use FluxUtil.pagedFluxError for listSessions null guard
Round 12 review feedback. Switched the null-updatedAfter guard in
ServiceBusSessionReceiverAsyncClient.listSessions(OffsetDateTime) from
`new PagedFlux<>(() -> monoError(LOGGER, ...))` to the standard helper
`pagedFluxError(LOGGER, ...)` from FluxUtil. Same observable behaviour for
callers (the PagedFlux errors with NullPointerException on subscribe), but
matches the convention every other Azure SDK PagedFlux argument-validation
site uses.
* test(servicebus): isolate getMessageSessionsNotFound from shared state
Round 13 review feedback. Use a local copy of the shared applicationProperties
field when adding the SessionNotFound error-condition so the entry doesn't
leak into subsequent 404 tests that share the field. The setup() method
initializes applicationProperties with STATUS_CODE=OK and never resets it
between tests, so a put() inside this test would have permanently mutated
the field.
* fix(servicebus): tighten timestamp assertion + sentinel comparison contract
Round 14 review feedback.
- ManagementChannel.getMessageSessions: changed the sentinel cap comparison
from `> 0` to `>= 0` so the comment ("at or beyond that instant is
clamped") matches the code. Equality is a no-op (clamping to the same
value), but routing it through the clamp explicitly keeps the documented
contract precise.
- ManagementChannelTests.getMessageSessionsUpdatedAfterMode: the assertion
only checked that the wire timestamp was a Date instance. Tightened to
assertEquals(Date.from(lastUpdated.toInstant()), ...) so a regression in
the OffsetDateTime -> Date conversion (e.g., a rounding bug or wrong
zone offset) would actually fail the test.
* fix(servicebus): strict UTF-8 cursor decoding + clearer async-error test
Round 15 review feedback.
- ServiceBusSessionReceiverAsyncClient.listSessionsInternal now uses a strict
UTF-8 CharsetDecoder (CodingErrorAction.REPORT for malformed and
unmappable input) when decoding the base64url-encoded lastSessionId
payload. Previously, new String(decoded, UTF_8) would silently substitute
U+FFFD for invalid UTF-8 sequences, which could send a corrupted session
ID to the broker as the cursor. Decode failures now surface as logged
IllegalArgumentException with a clear "not valid UTF-8" message. Added
test listSessionsRejectsNonUtf8ContinuationToken (token "0|__4" decodes
to bytes 0xFF 0xFE, which isn't valid UTF-8).
- ServiceBusSessionReceiverClientTest.listSessionsPropagatesAsyncError:
switched the stub failure from NullPointerException("'updatedAfter'
cannot be null.") to RuntimeException("Transient async failure."). The
test passes a non-null updatedAfter, so the previous exception type and
message were misleading — they suggested the test was checking the eager
null-validation path (which has its own dedicated test). The new wording
makes it clear that this test is about an unrelated transient async
failure propagating through the PagedIterable iteration.
* fix(servicebus): allocate fresh HttpHeaders per listSessions page
Round 16 review feedback. Replaced the shared static EMPTY_HEADERS field
with a per-response `new HttpHeaders()` allocation in
ServiceBusSessionReceiverAsyncClient.listSessionsInternal. HttpHeaders is
mutable and PagedResponseBase#getHeaders() exposes the stored reference,
so a static shared instance could leak mutations across every page and
every client globally. The per-page allocation is negligible and matches
the pattern used elsewhere in the SDK.
* fix(servicebus): wrap listSessions page fetch in tracing span
Round 17 review feedback. Wrapped fetchSessionPage in
tracer.traceMono("ServiceBus.listSessions", ...) so each page fetch emits
a distributed-tracing span. This matches the tracing pattern used by
acceptSession and acceptNextSession in the same client and makes the
paged listSessions/listSessions(updatedAfter) calls observable in
distributed traces.
* docs(servicebus): add CHANGELOG entry for listSessions feature
Round 18 review feedback. Added an entry under
"7.18.0-beta.2 (Unreleased) > Features Added" in
sdk/servicebus/azure-messaging-servicebus/CHANGELOG.md describing the new
listSessions API on both async and sync session receiver clients, and
referencing PR #48956.
* test(servicebus): correct getMessageSessionsNotFound rationale comment
Round 19 review feedback. JUnit 5's default lifecycle is PER_METHOD (no
@testinstance(PER_CLASS) on this class), so the applicationProperties
field is recreated per test instance and a put() couldn't actually leak
into subsequent tests. The local-copy approach is still useful for
keeping this test's response setup self-contained, so the code stays;
only the rationale comment is corrected to match the actual lifecycle.
* docs(servicebus): document active-messages sentinel clamp on listSessions
Round 20 review feedback. Added javadoc to listSessions(OffsetDateTime)
on both ServiceBusSessionReceiverAsyncClient and
ServiceBusSessionReceiverClient explaining that values at or beyond
the active-messages sentinel (10000-01-01T00:00:00Z UTC, matching
Track 1's SessionBrowser.MAXDATE) are clamped to that sentinel and
behave the same as the no-arg listSessions() overload. The clamp is
implemented in ManagementChannel and applies to both async and sync
paths (sync delegates to async).
* fix(servicebus): address round 21 review feedback on listSessions
Round 21 review feedback batch:
- ManagementChannel.getMessageSessions: reject null entries in the
sessions-ids array/iterable with IllegalStateException instead of
surfacing the literal string "null" as a session ID via String.valueOf.
- ServiceBusManagementNode.getMessageSessions javadoc: removed the .NET-
specific phrasing ("DateTime.MaxValue + 1ms") and replaced it with the
Track 1 Java sentinel reference (new Date(253402300800000L)).
- ManagementChannelTests.getMessageSessionsActiveSessionsMode: renamed to
getMessageSessionsActiveMessagesMode for terminology consistency with
the broker mode the test exercises.
- ServiceBusSessionReceiverClientTest.listSessionsRejectsNullUpdatedAfter
Eagerly: spelling "behaviour" -> "behavior" (American English).
- Added ManagementChannelTests.getMessageSessionsRejectsNullSessionIdEntry
to cover the new null-entry rejection path.
* test(servicebus): use local copy in getMessageSessionsNoContent
Round 22 review feedback. Mirror the round-19 fix: use a local
HashMap<>(applicationProperties) in getMessageSessionsNoContent instead
of mutating the shared field in-place. This matches the pattern already
used by getMessageSessionsNotFound and keeps the test's response setup
self-contained.
(JUnit 5's default PER_METHOD lifecycle means the field is recreated per
test instance, so a put() on the shared map can't actually leak across
tests; the local-copy approach is purely for in-test self-containment.)
* fix(servicebus): treat empty listSessions continuation token as end of pages
Round 23 review feedback. listSessionsInternal now returns Mono.empty()
when the continuation token passed to PagedFlux's next-page function is
an empty string, matching the convention used by
ServiceBusAdministrationAsyncClient.listQueuesNextPage and
listRulesNextPage in this same package (and the wider Azure SDK paging
contract: null or empty token = no more pages). This makes the API
tolerant of callers that persist the token to storage and read back an
empty string.
Added test listSessionsEmptyContinuationTokenCompletes to lock the
behavior in. Existing invalid-token tests still pass.
* feat(servicebus): propagate caller page size to listSessions broker request
Round 24 review feedback. Switch listSessionsInternal to the page-size-
aware PagedFlux constructor (Function<Integer, ...>, BiFunction<String,
Integer, ...>) so a caller's byPage(int) value flows through to the
management request's `top` parameter. When the caller doesn't request a
specific size (or requests a non-positive value), fall back to the
default of 100. Renamed LIST_SESSIONS_PAGE_SIZE to
LIST_SESSIONS_DEFAULT_PAGE_SIZE to reflect its new role and added a
helper resolvePageSize(Integer).
Also documented the page-size behavior on the public listSessions
overloads of both ServiceBusSessionReceiverAsyncClient (PagedFlux) and
ServiceBusSessionReceiverClient (PagedIterable).
Added test ServiceBusSessionReceiverAsyncClientTest.listSessionsHonors
CallerPageSize: caller asks for 25-item pages, mock asserts the top=25
flowed through to both first-page and next-page management calls.
* docs(servicebus): correct listSessionsInternal byPage comment
Round 25 review feedback. The comment on the PagedFlux constructor in
listSessionsInternal incorrectly stated that byPage(null) is routed to
the first-page supplier; in azure-core, byPage(null) actually returns
Flux.empty() (see ContinuablePagedFluxCore.byPage(C)). The comment also
overlooked that the next-page retriever can be invoked directly via
byPage(token) without any previous PagedResponse, so it must validate
the token it receives. Comment-only change; behavior is unchanged.
* docs(servicebus): clarify active-messages sentinel literal in javadoc
Round 26 review feedback. Java's OffsetDateTime.toString() renders the
year-10000 sentinel as "+10000-01-01T00:00Z" - the leading "+" is
required by ISO 8601 for years with more than four digits, so the
previous "10000-01-01T00:00:00Z" literal in the javadoc would not be
parseable via OffsetDateTime.parse(). Updated the docs to reference the
ManagementConstants.ACTIVE_MESSAGES_SENTINEL constant and the actual
+10000-01-01T00:00Z literal so callers don't get tripped up trying to
parse the documented value. Comment-only change across:
- ManagementConstants (the constant's own javadoc)
- ServiceBusSessionReceiverAsyncClient (listSessions(OffsetDateTime))
- ServiceBusSessionReceiverClient (listSessions(OffsetDateTime))
- ServiceBusManagementNode (getMessageSessions parameter)
- ManagementChannel (internal clamp comment, for consistency)
* docs(servicebus): drop implementation-package link from listSessions docs
Round 27 review feedback. Removed the {@link
ManagementConstants#ACTIVE_MESSAGES_SENTINEL} reference from the public
javadoc on listSessions(OffsetDateTime) of both
ServiceBusSessionReceiverAsyncClient and ServiceBusSessionReceiverClient.
Public API javadoc shouldn't link to types in the implementation
subpackage; instead, the sentinel value is now described in prose
(`new Date(253402300800000L)`, rendered by OffsetDateTime.toString() as
`+10000-01-01T00:00Z`, matching Track 1's SessionBrowser.MAXDATE).
* fix(servicebus): surface protocol errors and harden MessageSessionsResult immutability
Round 28 review feedback:
- ManagementChannel.getMessageSessions: a 200 OK with an unexpected
body type (null body, non-AmqpValue body, or AmqpValue whose payload
isn't a Map) now fails the operation with a clear IllegalStateException
instead of returning an empty page. Silently turning a protocol/
compatibility issue into an empty page would terminate pagination and
drop any remaining results. The "no sessions-ids key" branch still
returns an empty page, which is the legitimate "no more pages" signal.
Added test getMessageSessionsRejectsUnexpectedBodyType.
- MessageSessionsResult: the ctor now defensively copies the incoming
sessionIds list and exposes it via Collections.unmodifiableList(...),
so external mutation of the source list cannot alter the result after
construction. A null sessionIds argument is now rejected eagerly with
NullPointerException instead of NPE'ing at iteration time.
* fix(servicebus): protocol-error on unexpected sessions-ids payload type
Round 29 review feedback. ManagementChannel.getMessageSessions now fails
the operation with IllegalStateException (logged via
logExceptionAsWarning, matching the null-entry path) when the
sessions-ids value is non-null but neither Object[] nor Iterable<?>,
e.g. a String. Previously this silently fell back to an empty list,
which would have terminated pagination and dropped any remaining
results on a broker/library payload-shape change. Mirrors the round-28
fix for the body-shape branch.
Added test getMessageSessionsRejectsUnexpectedSessionIdsPayloadType.
* fix(servicebus): require strict monotonic forward progress on response skip
Round 30 review feedback. ManagementChannel.readResponseSkip now
requires the broker-returned skip to be strictly greater than
requestSkip; equal would re-fetch the same page, smaller would cursor
backwards, and either case risks infinite loops or duplicate results.
Both cases now route through computeFallbackSkip(requestSkip, pageSize)
just like the missing/non-numeric/negative/out-of-range cases.
(The previous check was responseSkip >= 0; the new check responseSkip >
requestSkip subsumes that guard since requestSkip is validated as
non-negative at the call site.)
Added test getMessageSessionsFallsBackWhenServerSkipIsNotMonotonic
covering the equal-skip stall case.
* test(servicebus): use OffsetDateTime.MAX in getMessageSessionsCapsYear
Round 31 review feedback. Replaced the hand-rolled "year 99999" value
with OffsetDateTime.MAX so the test would actually fail (with
ArithmeticException from Date.from) if the clamp were moved after the
java.util.Date conversion or removed altogether. Year 99999 doesn't
overflow Date.from, so the previous setup didn't actually exercise the
overflow protection that the test was meant to lock in. The clamping
assertion (cap to 253_402_300_800_000 ms) is unchanged.
* fix(servicebus): rename updatedAfter to sessionStateUpdatedAfter for cross-SDK parity
---------
Co-authored-by: Eldert Grootenboer (from Dev Box) <egrootenboer@microsoft.com>1 parent 8f0429d commit 6074743
10 files changed
Lines changed: 1184 additions & 1 deletion
File tree
- sdk/servicebus/azure-messaging-servicebus
- src
- main/java/com/azure/messaging/servicebus
- implementation
- test/java/com/azure/messaging/servicebus
- implementation
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
| 8 | + | |
7 | 9 | | |
8 | 10 | | |
9 | 11 | | |
| |||
Lines changed: 171 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
12 | 16 | | |
13 | 17 | | |
| 18 | + | |
14 | 19 | | |
15 | 20 | | |
16 | 21 | | |
17 | 22 | | |
18 | 23 | | |
19 | 24 | | |
20 | 25 | | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
21 | 32 | | |
22 | 33 | | |
23 | 34 | | |
| 35 | + | |
24 | 36 | | |
25 | 37 | | |
26 | 38 | | |
| |||
149 | 161 | | |
150 | 162 | | |
151 | 163 | | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
152 | 172 | | |
153 | 173 | | |
154 | 174 | | |
| |||
292 | 312 | | |
293 | 313 | | |
294 | 314 | | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
295 | 466 | | |
296 | 467 | | |
297 | 468 | | |
| |||
Lines changed: 43 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
11 | 12 | | |
12 | 13 | | |
13 | 14 | | |
14 | 15 | | |
| 16 | + | |
15 | 17 | | |
16 | 18 | | |
17 | 19 | | |
| |||
201 | 203 | | |
202 | 204 | | |
203 | 205 | | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
204 | 247 | | |
205 | 248 | | |
206 | 249 | | |
| |||
0 commit comments