001/* 002 * Copyright 2022-2026 Revetware LLC. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.soklet; 018 019import org.jspecify.annotations.NonNull; 020import org.jspecify.annotations.Nullable; 021 022import javax.annotation.concurrent.NotThreadSafe; 023 024import static java.util.Objects.requireNonNull; 025 026/** Indicates that a shutdown component terminated unexpectedly after readiness. */ 027@NotThreadSafe 028public final class SokletUnexpectedTerminationException 029 extends SokletLifecycleException { 030 @NonNull 031 private final UnexpectedShutdownComponentTermination 032 unexpectedShutdownComponentTermination; 033 @Nullable 034 private final InternalTerminationEvent internalUnexpectedTermination; 035 036 SokletUnexpectedTerminationException( 037 @NonNull ShutdownResult shutdownResult, 038 @NonNull Throwable cause) { 039 this(null, shutdownResult, cause); 040 } 041 042 SokletUnexpectedTerminationException( 043 @Nullable InternalTerminationEvent internalUnexpectedTermination, 044 @NonNull ShutdownResult shutdownResult, 045 @NonNull Throwable cause) { 046 super("A Soklet transport terminated unexpectedly", 047 requireNonNull(shutdownResult), requireNonNull(cause)); 048 this.unexpectedShutdownComponentTermination = shutdownResult 049 .getUnexpectedShutdownComponentTermination() 050 .orElseThrow(() -> new IllegalArgumentException( 051 "Unexpected termination exception requires event evidence")); 052 this.internalUnexpectedTermination = internalUnexpectedTermination; 053 } 054 055 SokletUnexpectedTerminationException( 056 @NonNull InternalTerminationEvent unexpectedTermination, 057 @NonNull InternalShutdownResult shutdownResult) { 058 this(unexpectedTermination, shutdownResult, 059 requireNonNull(unexpectedTermination).cause().orElseGet(() -> 060 new IllegalStateException( 061 "A transport terminated without a failure cause"))); 062 } 063 064 SokletUnexpectedTerminationException( 065 @NonNull InternalTerminationEvent unexpectedTermination, 066 @NonNull InternalShutdownResult shutdownResult, 067 @NonNull Throwable cause) { 068 this(unexpectedTermination, 069 ShutdownResult.fromInternal(requireNonNull(shutdownResult), null, 070 inferShutdownComponentType(shutdownResult), 071 requireNonNull(unexpectedTermination).cause().orElse(null)), 072 cause); 073 } 074 075 /** @return the first premature shutdown component termination */ 076 @NonNull 077 public UnexpectedShutdownComponentTermination getUnexpectedShutdownComponentTermination() { 078 return this.unexpectedShutdownComponentTermination; 079 } 080 081 @NonNull 082 InternalTerminationEvent getInternalUnexpectedTermination() { 083 return requireNonNull(this.internalUnexpectedTermination, 084 "No internal termination event is retained"); 085 } 086 087 @NonNull 088 private static ShutdownComponentType inferShutdownComponentType( 089 @NonNull InternalShutdownResult result) { 090 return requireNonNull(result).participantResults().stream() 091 .filter(participant -> participant.disposition() 092 == InternalLifecycleComponentShutdownDisposition 093 .UNEXPECTED_TERMINATION) 094 .map(participant -> ShutdownComponentType.valueOf( 095 participant.kind().name())).findFirst() 096 .orElseGet(() -> result.participantResults().stream() 097 .map(participant -> ShutdownComponentType.valueOf( 098 participant.kind().name())).findFirst() 099 .orElse(ShutdownComponentType.FRAMEWORK)); 100 } 101}