Input:
import com.infineon.esim.lpa.core.es9plus.Es9PlusInterface;
import com.infineon.esim.util.Log;
import com.gsma.sgp.messages.rspdefinitions.CancelSessionReason;
import com.gsma.sgp.messages.rspdefinitions.CancelSessionRequest;
import com.gsma.sgp.messages.rspdefinitions.CancelSessionRequestEs9;
import com.gsma.sgp.messages.rspdefinitions.CancelSessionResponse;
import com.gsma.sgp.messages.rspdefinitions.CancelSessionResponseEs9;
import com.infineon.esim.lpa.core.dtos.ProfileDownloadSession;
import com.infineon.esim.lpa.core.es10.Es10Interface;

Context:
core/src/main/java/com/infineon/esim/lpa/core/worker/remote/CancelSessionWorker.java
/*
 * THE SOURCE CODE AND ITS RELATED DOCUMENTATION IS PROVIDED "AS IS". INFINEON
 * TECHNOLOGIES MAKES NO OTHER WARRANTY OF ANY KIND,WHETHER EXPRESS,IMPLIED OR,
 * STATUTORY AND DISCLAIMS ANY AND ALL IMPLIED WARRANTIES OF MERCHANTABILITY,
 * SATISFACTORY QUALITY, NON INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * THE SOURCE CODE AND DOCUMENTATION MAY INCLUDE ERRORS. INFINEON TECHNOLOGIES
 * RESERVES THE RIGHT TO INCORPORATE MODIFICATIONS TO THE SOURCE CODE IN LATER
 * REVISIONS OF IT, AND TO MAKE IMPROVEMENTS OR CHANGES IN THE DOCUMENTATION OR
 * THE PRODUCTS OR TECHNOLOGIES DESCRIBED THEREIN AT ANY TIME.
 *
 * INFINEON TECHNOLOGIES SHALL NOT BE LIABLE FOR ANY DIRECT, INDIRECT OR
 * CONSEQUENTIAL DAMAGE OR LIABILITY ARISING FROM YOUR USE OF THE SOURCE CODE OR
 * ANY DOCUMENTATION, INCLUDING BUT NOT LIMITED TO, LOST REVENUES, DATA OR
 * PROFITS, DAMAGES OF ANY SPECIAL, INCIDENTAL OR CONSEQUENTIAL NATURE, PUNITIVE
 * DAMAGES, LOSS OF PROPERTY OR LOSS OF PROFITS ARISING OUT OF OR IN CONNECTION
 * WITH THIS AGREEMENT, OR BEING UNUSABLE, EVEN IF ADVISED OF THE POSSIBILITY OR
 * PROBABILITY OF SUCH DAMAGES AND WHETHER A CLAIM FOR SUCH DAMAGE IS BASED UPON
 * WARRANTY, CONTRACT, TORT, NEGLIGENCE OR OTHERWISE.
 *
 * (C)Copyright INFINEON TECHNOLOGIES All rights reserved
 */

package com.infineon.esim.lpa.core.worker.remote;

public class CancelSessionWorker {
    private static final String TAG = CancelSessionWorker.class.getName();

    private final ProfileDownloadSession profileDownloadSession;

    private final Es10Interface es10Interface;
    private final Es9PlusInterface es9PlusInterface;

    public CancelSessionWorker(ProfileDownloadSession profileDownloadSession) {
        this.profileDownloadSession = profileDownloadSession;
        this.es10Interface = profileDownloadSession.getEs10Interface();

messages/src/main/java/com/gsma/sgp/messages/rspdefinitions/CancelSessionRequestEs9.java
public class CancelSessionRequestEs9 implements BerType, Serializable {

	private static final long serialVersionUID = 1L;

	public static final BerTag tag = new BerTag(BerTag.CONTEXT_CLASS, BerTag.CONSTRUCTED, 65);

	public byte[] code = null;
	private TransactionId transactionId = null;
	private CancelSessionResponse cancelSessionResponse = null;

	public CancelSessionRequestEs9() {
	}

	public CancelSessionRequestEs9(byte[] code) {
		this.code = code;
	}

	public void setTransactionId(TransactionId transactionId) {
		this.transactionId = transactionId;
	}

	public TransactionId getTransactionId() {
		return transactionId;
	}

	public void setCancelSessionResponse(CancelSessionResponse cancelSessionResponse) {
		this.cancelSessionResponse = cancelSessionResponse;
	}

	public CancelSessionResponse getCancelSessionResponse() {
		return cancelSessionResponse;
	}

	public int encode(OutputStream reverseOS) throws IOException {
		return encode(reverseOS, true);
	}

	public int encode(OutputStream reverseOS, boolean withTag) throws IOException {

		if (code != null) {
			for (int i = code.length - 1; i >= 0; i--) {
				reverseOS.write(code[i]);
			}
			if (withTag) {
				return tag.encode(reverseOS) + code.length;
			}
			return code.length;
		}

		int codeLength = 0;
		codeLength += cancelSessionResponse.encode(reverseOS, false);
		// write tag: CONTEXT_CLASS, CONSTRUCTED, 1
		reverseOS.write(0xA1);
		codeLength += 1;

		codeLength += transactionId.encode(reverseOS, false);
		// write tag: CONTEXT_CLASS, PRIMITIVE, 0
		reverseOS.write(0x80);
		codeLength += 1;

		codeLength += BerLength.encodeLength(reverseOS, codeLength);

		if (withTag) {
			codeLength += tag.encode(reverseOS);
		}

		return codeLength;

	}

	public int decode(InputStream is) throws IOException {
		return decode(is, true);
	}

	public int decode(InputStream is, boolean withTag) throws IOException {
		int codeLength = 0;
		int subCodeLength = 0;
		BerTag berTag = new BerTag();

		if (withTag) {
			codeLength += tag.decodeAndCheck(is);
		}

		BerLength length = new BerLength();
		codeLength += length.decode(is);

		int totalLength = length.val;
		codeLength += totalLength;

		subCodeLength += berTag.decode(is);
		if (berTag.equals(BerTag.CONTEXT_CLASS, BerTag.PRIMITIVE, 0)) {
			transactionId = new TransactionId();
			subCodeLength += transactionId.decode(is, false);
			subCodeLength += berTag.decode(is);
		}
		else {
			throw new IOException("Tag does not match the mandatory sequence element tag.");
		}

		if (berTag.equals(BerTag.CONTEXT_CLASS, BerTag.CONSTRUCTED, 1)) {
			cancelSessionResponse = new CancelSessionResponse();
			subCodeLength += cancelSessionResponse.decode(is, false);
			if (subCodeLength == totalLength) {
				return codeLength;
			}
		}
		throw new IOException("Unexpected end of sequence, length tag: " + totalLength + ", actual sequence length: " + subCodeLength);

	}

	public void encodeAndSave(int encodingSizeGuess) throws IOException {
		ReverseByteArrayOutputStream reverseOS = new ReverseByteArrayOutputStream(encodingSizeGuess);
		encode(reverseOS, false);
		code = reverseOS.getArray();
	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		appendAsString(sb, 0);
		return sb.toString();
	}

	public void appendAsString(StringBuilder sb, int indentLevel) {

		sb.append("{");
		sb.append("\n");
		for (int i = 0; i < indentLevel + 1; i++) {
			sb.append("\t");
		}
		if (transactionId != null) {
			sb.append("transactionId: ").append(transactionId);
		}
		else {
			sb.append("transactionId: <empty-required-field>");
		}

		sb.append(",\n");
		for (int i = 0; i < indentLevel + 1; i++) {
			sb.append("\t");
		}
		if (cancelSessionResponse != null) {
			sb.append("cancelSessionResponse: ");
			cancelSessionResponse.appendAsString(sb, indentLevel + 1);
		}
		else {
			sb.append("cancelSessionResponse: <empty-required-field>");
		}

		sb.append("\n");
		for (int i = 0; i < indentLevel; i++) {
			sb.append("\t");
		}
		sb.append("}");
	}

}

messages/src/main/java/com/gsma/sgp/messages/rspdefinitions/CancelSessionReason.java
public class CancelSessionReason extends BerInteger {

	private static final long serialVersionUID = 1L;

	public CancelSessionReason() {
	}

	public CancelSessionReason(byte[] code) {
		super(code);
	}

	public CancelSessionReason(BigInteger value) {
		super(value);
	}

	public CancelSessionReason(long value) {
		super(value);
	}

}

core/src/main/java/com/infineon/esim/lpa/core/es10/Es10Interface.java
@SuppressWarnings("unused")
public class Es10Interface {
    private static final String TAG = Es10Interface.class.getName();

    private final EuiccChannel euiccChannel;

    public Es10Interface(EuiccChannel euiccChannel) {
        this.euiccChannel = euiccChannel;
    }

    // ES10a

    public EuiccConfiguredAddressesResponse es10a_getEuiccConfiguredAddresses() throws Exception {
        EuiccConfiguredAddressesRequest euiccConfiguredAddressesRequest = new EuiccConfiguredAddressesRequest();

        Log.debug(TAG, "ES10 -> : " + euiccConfiguredAddressesRequest);
        EuiccConfiguredAddressesResponse euiccConfiguredAddressesResponse = sendCommand(euiccConfiguredAddressesRequest, EuiccConfiguredAddressesResponse.class);
        Log.debug(TAG, "ES10 <- : " + euiccConfiguredAddressesResponse);

        return euiccConfiguredAddressesResponse;
    }

    public SetDefaultDpAddressResponse es10a_setDefaultDpAddress(BerUTF8String defaultDpAddress) throws Exception {
        SetDefaultDpAddressRequest setDefaultDpAddressRequest = new SetDefaultDpAddressRequest();
        setDefaultDpAddressRequest.setDefaultDpAddress(defaultDpAddress);

        Log.debug(TAG, "ES10 -> : " + setDefaultDpAddressRequest);
        SetDefaultDpAddressResponse setDefaultDpAddressResponse = sendCommand(setDefaultDpAddressRequest, SetDefaultDpAddressResponse.class);
        Log.debug(TAG, "ES10 <- : " + setDefaultDpAddressResponse);

        return setDefaultDpAddressResponse;
    }

    // ES10b

    public PrepareDownloadResponse es10b_prepareDownloadRequest(PrepareDownloadRequest prepareDownloadRequest) throws Exception {
        return sendCommand(prepareDownloadRequest, PrepareDownloadResponse.class);
    }

    public ProfileInstallationResult es10b_loadBoundProfilePackage(SegmentedBoundProfilePackage segmentedBoundProfilePackage) throws Exception {
        List<List<String>> segments = segmentedBoundProfilePackage.getSegments();

        for(int i = 0; i < (segments.size() - 1); i++) {
            sendCommand(segments.get(i), null);
        }

        return sendCommand(segments.get(segments.size() - 1), ProfileInstallationResult.class);
    }

    public GetEuiccChallengeResponse es10b_getEuiccChallenge() throws Exception {
        GetEuiccChallengeRequest getEuiccChallengeRequest = new GetEuiccChallengeRequest();

        return sendCommand(getEuiccChallengeRequest, GetEuiccChallengeResponse.class);
    }

    public EUICCInfo1 es10b_getEuiccInfo1() throws Exception {
        GetEuiccInfo1Request getEuiccInfo1Request = new GetEuiccInfo1Request();

        return sendCommand(getEuiccInfo1Request, EUICCInfo1.class);
    }

    public EUICCInfo2 es10b_getEuiccInfo2() throws Exception {
        GetEuiccInfo2Request getEuiccInfo2Request = new GetEuiccInfo2Request();

        return sendCommand(getEuiccInfo2Request, EUICCInfo2.class);
    }

    public ListNotificationResponse es10b_listNotification(NotificationEvent notificationEvent) throws Exception {
        ListNotificationRequest listNotificationRequest = new ListNotificationRequest();
        listNotificationRequest.setProfileManagementOperation(notificationEvent);

        return sendCommand(listNotificationRequest, ListNotificationResponse.class);
    }

    public RetrieveNotificationsListResponse es10b_retrieveNotificationsListBySeqNumber(BerInteger seqNumber) throws Exception {
        RetrieveNotificationsListRequest.SearchCriteria searchCriteria = new RetrieveNotificationsListRequest.SearchCriteria();
        searchCriteria.setSeqNumber(seqNumber);

        return es10b_retrieveNotificationsListBySeqNumber(searchCriteria);
    }

    public RetrieveNotificationsListResponse es10b_retrieveNotificationsListByNotificationEvent(NotificationEvent notificationEvent) throws Exception {
        RetrieveNotificationsListRequest.SearchCriteria searchCriteria = new RetrieveNotificationsListRequest.SearchCriteria();
        searchCriteria.setProfileManagementOperation(notificationEvent);

        return es10b_retrieveNotificationsListBySeqNumber(searchCriteria);
    }

    private RetrieveNotificationsListResponse es10b_retrieveNotificationsListBySeqNumber(RetrieveNotificationsListRequest.SearchCriteria searchCriteria) throws Exception {
        RetrieveNotificationsListRequest retrieveNotificationsListRequest = new RetrieveNotificationsListRequest();
        retrieveNotificationsListRequest.setSearchCriteria(searchCriteria);

        return sendCommand(retrieveNotificationsListRequest, RetrieveNotificationsListResponse.class);
    }

    public NotificationSentResponse es10b_removeNotificationFromList(BerInteger seqNumber) throws Exception {
        NotificationSentRequest notificationSentRequest = new NotificationSentRequest();
        notificationSentRequest.setSeqNumber(seqNumber);

        return sendCommand(notificationSentRequest,NotificationSentResponse.class);
    }

    public AuthenticateServerResponse es10b_authenticateServer(AuthenticateServerRequest authenticateServerRequest) throws Exception {
        return sendCommand(authenticateServerRequest, AuthenticateServerResponse.class);
    }

    public CancelSessionResponse es10b_cancelSession(CancelSessionRequest cancelSessionRequest) throws Exception {
        return sendCommand(cancelSessionRequest, CancelSessionResponse.class);
    }

    // ES10c

    public ProfileInfoListResponse es10c_getProfilesInfoAll() throws Exception {
        ProfileInfoListRequest profileInfoListRequest = new ProfileInfoListRequest();

        return sendCommand(profileInfoListRequest, ProfileInfoListResponse.class);
    }

    public EnableProfileResponse es10c_enableProfileByIccid(Iccid iccid, BerBoolean refreshFlag) throws Exception {
        EnableProfileRequest.ProfileIdentifier profileIdentifier = new EnableProfileRequest.ProfileIdentifier();
        profileIdentifier.setIccid(iccid);

        return es10c_enableProfile(profileIdentifier,refreshFlag);
    }

    public EnableProfileResponse es10c_enableProfileByIsdpAid(OctetTo16 isdpAid, BerBoolean refreshFlag) throws Exception {
        EnableProfileRequest.ProfileIdentifier profileIdentifier = new EnableProfileRequest.ProfileIdentifier();
        profileIdentifier.setIsdpAid(isdpAid);

        return es10c_enableProfile(profileIdentifier, refreshFlag);
    }

    private EnableProfileResponse es10c_enableProfile(EnableProfileRequest.ProfileIdentifier profileIdentifier, BerBoolean refreshFlag) throws Exception {
        EnableProfileRequest enableProfileRequest = new EnableProfileRequest();
        enableProfileRequest.setProfileIdentifier(profileIdentifier);
        enableProfileRequest.setRefreshFlag(refreshFlag);

        return sendCommand(enableProfileRequest, EnableProfileResponse.class);
    }

    public DisableProfileResponse es10c_disableProfileByIccid(Iccid iccid, BerBoolean refreshFlag) throws Exception {
        DisableProfileRequest.ProfileIdentifier profileIdentifier = new DisableProfileRequest.ProfileIdentifier();
        profileIdentifier.setIccid(iccid);

        return es10c_disableProfile(profileIdentifier, refreshFlag);
    }

    public DisableProfileResponse es10c_disableProfileByIsdpAid(OctetTo16 isdpAid, BerBoolean refreshFlag) throws Exception {
        DisableProfileRequest.ProfileIdentifier profileIdentifier = new DisableProfileRequest.ProfileIdentifier();
        profileIdentifier.setIsdpAid(isdpAid);

        return es10c_disableProfile(profileIdentifier, refreshFlag);
    }

    private DisableProfileResponse es10c_disableProfile(DisableProfileRequest.ProfileIdentifier profileIdentifier, BerBoolean refreshFlag) throws Exception {
        DisableProfileRequest disableProfileRequest = new DisableProfileRequest();
        disableProfileRequest.setProfileIdentifier(profileIdentifier);
        disableProfileRequest.setRefreshFlag(refreshFlag);

        return sendCommand(disableProfileRequest, DisableProfileResponse.class);
    }

    public DeleteProfileResponse es10c_deleteProfileByIccid(Iccid iccid) throws Exception {
        DeleteProfileRequest deleteProfileRequest = new DeleteProfileRequest();
        deleteProfileRequest.setIccid(iccid);

        return sendCommand(deleteProfileRequest, DeleteProfileResponse.class);
    }

    public DeleteProfileResponse es10c_deleteProfileByIsdpAid(OctetTo16 isdpAid) throws Exception {
        DeleteProfileRequest deleteProfileRequest = new DeleteProfileRequest();
        deleteProfileRequest.setIsdpAid(isdpAid);

        return sendCommand(deleteProfileRequest, DeleteProfileResponse.class);
    }

    public EuiccMemoryResetResponse es10c_eUiccMemoryReset(BerBitString resetOptions) throws Exception {
        EuiccMemoryResetRequest euiccMemoryResetRequest = new EuiccMemoryResetRequest();
        euiccMemoryResetRequest.setResetOptions(resetOptions);

        return sendCommand(euiccMemoryResetRequest, EuiccMemoryResetResponse.class);
    }

    public GetEuiccDataResponse es10c_getEid() throws Exception {
        GetEuiccDataRequest getEuiccDataRequest = new GetEuiccDataRequest();
        getEuiccDataRequest.setTagList(new Octet1(Bytes.decodeHexString("5A")));

        return sendCommand(getEuiccDataRequest, GetEuiccDataResponse.class);
    }

    public SetNicknameResponse es10c_setNickname(Iccid iccid, String nickname) throws Exception {
        SetNicknameRequest setNicknameRequest = new SetNicknameRequest();
        setNicknameRequest.setIccid(iccid);
        setNicknameRequest.setProfileNickname(new BerUTF8String(nickname));

        return sendCommand(setNicknameRequest, SetNicknameResponse.class);
    }

    public GetRatResponse es10c_getRat() throws Exception {
        GetRatRequest getRatRequest = new GetRatRequest();

        return sendCommand(getRatRequest, GetRatResponse.class);
    }

    // INTERNAL

    private <T extends BerType> T sendCommand(BerType berRequest, Class<T> berResponseClass) throws Exception {
        List<String> requests =  TransportCommand.getTransportCommands(berRequest);

        Log.debug(TAG, "ES10 -> : " + berRequest.getClass().getSimpleName() + "\n" + berRequest);
        String response = transmitApdus(requests);

        if(Apdu.isSuccessResponse(response)) {
            T berResponse = Ber.createFromEncodedHexString(berResponseClass, response);
            Log.debug(TAG, "ES10 <- : " + berResponse.getClass().getSimpleName() + "\n" + berResponse);

            return berResponse;
        } else {
            throw new Exception("Error: APDU response is no success: " + Apdu.getStatusWord(response));
        }
    }

    private <T extends BerType> T sendCommand(List<String> encodedRequests, Class<T> berResponseClass) throws Exception {
        List<String> requests =  TransportCommand.getTransportCommands(encodedRequests);

        String response = transmitApdus(requests);

        if(Apdu.isSuccessResponse(response)) {
            if(berResponseClass != null) {
                return Ber.createFromEncodedHexString(berResponseClass, response);
            } else {
                return null;
            }
        } else {
            throw new Exception("Error: APDU response is no success: " + Apdu.getStatusWord(response));
        }
    }

    private String transmitApdus(List<String> apduRequests) throws Exception {
        List<String> apduResponses;
        String finalApduResponse = null;

        Log.debug(TAG, "ES10 - Transmit APDUs requests:  " + apduRequests);
        apduResponses = euiccChannel.transmitAPDUS(apduRequests);
        Log.debug(TAG, "ES10 - Transmit APDUs responses:  " + apduResponses);

        // Return first response APDU that contains data
        for (String apduResponse : apduResponses) {
            finalApduResponse = apduResponse;
            if (Apdu.doesResponseContainData(apduResponse)) {
                return apduResponse;
            }
        }

        return finalApduResponse;
    }
}

messages/src/main/java/com/gsma/sgp/messages/rspdefinitions/CancelSessionResponseEs9.java
public class CancelSessionResponseEs9 implements BerType, Serializable {

	private static final long serialVersionUID = 1L;

	public byte[] code = null;
	public static final BerTag tag = new BerTag(BerTag.CONTEXT_CLASS, BerTag.CONSTRUCTED, 65);

	private CancelSessionOk cancelSessionOk = null;
	private BerInteger cancelSessionError = null;

	public CancelSessionResponseEs9() {
	}

	public CancelSessionResponseEs9(byte[] code) {
		this.code = code;
	}

	public void setCancelSessionOk(CancelSessionOk cancelSessionOk) {
		this.cancelSessionOk = cancelSessionOk;
	}

	public CancelSessionOk getCancelSessionOk() {
		return cancelSessionOk;
	}

	public void setCancelSessionError(BerInteger cancelSessionError) {
		this.cancelSessionError = cancelSessionError;
	}

	public BerInteger getCancelSessionError() {
		return cancelSessionError;
	}

	public int encode(OutputStream reverseOS) throws IOException {
		return encode(reverseOS, true);
	}

	public int encode(OutputStream reverseOS, boolean withTag) throws IOException {

		if (code != null) {
			for (int i = code.length - 1; i >= 0; i--) {
				reverseOS.write(code[i]);
			}
			if (withTag) {
				return tag.encode(reverseOS) + code.length;
			}
			return code.length;
		}

		int codeLength = 0;
		if (cancelSessionError != null) {
			codeLength += cancelSessionError.encode(reverseOS, false);
			// write tag: CONTEXT_CLASS, PRIMITIVE, 1
			reverseOS.write(0x81);
			codeLength += 1;
			codeLength += BerLength.encodeLength(reverseOS, codeLength);
			if (withTag) {
				codeLength += tag.encode(reverseOS);
			}
			return codeLength;
		}

		if (cancelSessionOk != null) {
			codeLength += cancelSessionOk.encode(reverseOS, false);
			// write tag: CONTEXT_CLASS, CONSTRUCTED, 0
			reverseOS.write(0xA0);
			codeLength += 1;
			codeLength += BerLength.encodeLength(reverseOS, codeLength);
			if (withTag) {
				codeLength += tag.encode(reverseOS);
			}
			return codeLength;
		}

		throw new IOException("Error encoding CHOICE: No element of CHOICE was selected.");
	}

	public int decode(InputStream is) throws IOException {
		return decode(is, true);
	}

	public int decode(InputStream is, boolean withTag) throws IOException {
		int codeLength = 0;
		BerLength length = new BerLength();
		BerTag berTag = new BerTag();

		if (withTag) {
			codeLength += tag.decodeAndCheck(is);
		}

		codeLength += length.decode(is);
		codeLength += berTag.decode(is);

		if (berTag.equals(BerTag.CONTEXT_CLASS, BerTag.CONSTRUCTED, 0)) {
			cancelSessionOk = new CancelSessionOk();
			codeLength += cancelSessionOk.decode(is, false);
			return codeLength;
		}

		if (berTag.equals(BerTag.CONTEXT_CLASS, BerTag.PRIMITIVE, 1)) {
			cancelSessionError = new BerInteger();
			codeLength += cancelSessionError.decode(is, false);
			return codeLength;
		}

		throw new IOException("Error decoding CHOICE: Tag " + berTag + " matched to no item.");
	}

	public void encodeAndSave(int encodingSizeGuess) throws IOException {
		ReverseByteArrayOutputStream reverseOS = new ReverseByteArrayOutputStream(encodingSizeGuess);
		encode(reverseOS, false);
		code = reverseOS.getArray();
	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		appendAsString(sb, 0);
		return sb.toString();
	}

	public void appendAsString(StringBuilder sb, int indentLevel) {

		if (cancelSessionOk != null) {
			sb.append("cancelSessionOk: ");
			cancelSessionOk.appendAsString(sb, indentLevel + 1);
			return;
		}

		if (cancelSessionError != null) {
			sb.append("cancelSessionError: ").append(cancelSessionError);
			return;
		}

		sb.append("<none>");
	}

}

app/src/test/java/com/infineon/esim/util/Log.java
final public class Log {

    // Ref:
    // https://stackoverflow.com/questions/8355632/how-do-you-usually-tag-log-entries-android
    public static String getFileLineNumber() {
        String info = "";
        final StackTraceElement[] ste = Thread.currentThread().getStackTrace();
        for (int i = 0; i < ste.length; i++) {
            if (ste[i].getMethodName().equals("getFileLineNumber")) {
                info = "("+ste[i + 1].getFileName() + ":" + ste[i + 1].getLineNumber()+")";
            }
        }
        return info;
    }

    public static void verbose(final String tag, final String msg) {
        System.out.println("V - " + tag + ": " + msg);
    }

    public static void debug(final String tag, final String msg) {
        System.out.println("D - " + tag + ": " + msg);
    }

    public static void info(final String tag, final String msg) {
        System.out.println("I- " + tag + ": " + msg);
    }

    public static void error(final String msg) {
        System.out.println("E- " + msg);
    }

    public static void error(final String tag, final String msg) {
        System.out.println("E- " + tag + ": " + msg);
    }

    public static void error(final String tag, final String msg, final Throwable error) {
        System.out.println("E- " + tag + ": " + msg);
        error.printStackTrace();
    }
}

core/src/main/java/com/infineon/esim/lpa/core/es9plus/Es9PlusInterface.java
public class Es9PlusInterface {
    private static final String TAG = Es9PlusInterface.class.getName();

    private static final Gson GS = new GsonBuilder().disableHtmlEscaping().create();

    private static final String INITIATE_AUTHENTICATION_PATH = "/gsma/rsp2/es9plus/initiateAuthentication";
    private static final String AUTHENTICATE_CLIENT_PATH = "/gsma/rsp2/es9plus/authenticateClient";
    private static final String GET_BOUND_PROFILE_PACKAGE_PATH = "/gsma/rsp2/es9plus/getBoundProfilePackage";
    private static final String HANDLE_NOTIFICATION_PATH = "/gsma/rsp2/es9plus/handleNotification";
    private static final String CANCEL_SESSION_PATH = "/gsma/rsp2/es9plus/cancelSession";

    private final HttpsClient httpsClient;

    private String smdpAddress;

    private FunctionExecutionStatus lastFunctionExecutionStatus = null;

    public Es9PlusInterface() {
        this.httpsClient = new HttpsClient();
    }

    public void setSmdpAddress(String smdpAddress) {
        this.smdpAddress = smdpAddress;
    }

    public InitiateAuthenticationResponse initiateAuthentication(InitiateAuthenticationRequest initiateAuthenticationRequest) throws Exception {
        ensureSmdpAddressIsAvailable();

        Log.debug(TAG, "ES9+ -> : " + initiateAuthenticationRequest);

        InitiateAuthenticationReq initiateAuthenticationReq = new InitiateAuthenticationReq();
        initiateAuthenticationReq.setRequest(initiateAuthenticationRequest);

        HttpResponse httpResponse = httpsClient.sendRequest(GS.toJson(initiateAuthenticationReq), smdpAddress, INITIATE_AUTHENTICATION_PATH, true);
        checkHttpStatusCode("InitiateAuthentication", httpResponse, HttpsURLConnection.HTTP_OK);

        InitiateAuthenticationResp initiateAuthenticationResp = GS.fromJson(httpResponse.getContent(), InitiateAuthenticationResp.class);
        checkFunctionExecutionStatus("InitiateAuthentication", initiateAuthenticationResp);

        InitiateAuthenticationResponse initiateAuthenticationResponse = initiateAuthenticationResp.getResponse();
        Log.debug(TAG, "ES9+ <- : " + initiateAuthenticationResponse);

        return initiateAuthenticationResponse;
    }

    public AuthenticateClientResponseEs9 authenticateClient(AuthenticateClientRequest authenticateClientRequest) throws Exception {
        ensureSmdpAddressIsAvailable();

        Log.debug(TAG, "ES9+ -> : " + authenticateClientRequest);

        AuthenticateClientReq authenticateClientReq = new AuthenticateClientReq();
        authenticateClientReq.setRequest(authenticateClientRequest);

        HttpResponse httpResponse = httpsClient.sendRequest(GS.toJson(authenticateClientReq), smdpAddress, AUTHENTICATE_CLIENT_PATH, true);
        checkHttpStatusCode("AuthenticateClient", httpResponse, HttpsURLConnection.HTTP_OK);

        AuthenticateClientResp authenticateClientResp = GS.fromJson(httpResponse.getContent(), AuthenticateClientResp.class);
        checkFunctionExecutionStatus("AuthenticateClient", authenticateClientResp);

        AuthenticateClientResponseEs9 authenticateClientResponseEs9 = authenticateClientResp.getResponse();
        Log.debug(TAG, "ES9+ <- : " + authenticateClientResponseEs9);

        return authenticateClientResponseEs9;
    }

    public GetBoundProfilePackageResponse getBoundProfilePackage(GetBoundProfilePackageRequest getBoundProfilePackageRequest) throws Exception {
        ensureSmdpAddressIsAvailable();

        Log.debug(TAG, "ES9+ -> : " + getBoundProfilePackageRequest);

        GetBoundProfilePackageReq getBoundProfilePackageReq = new GetBoundProfilePackageReq();
        getBoundProfilePackageReq.setRequest(getBoundProfilePackageRequest);

        HttpResponse httpResponse = httpsClient.sendRequest(GS.toJson(getBoundProfilePackageReq), smdpAddress, GET_BOUND_PROFILE_PACKAGE_PATH, true);
        checkHttpStatusCode("GetBoundProfilePackage", httpResponse, HttpsURLConnection.HTTP_OK);

        GetBoundProfilePackageResp getBoundProfilePackageResp = GS.fromJson(httpResponse.getContent(), GetBoundProfilePackageResp.class);
        checkFunctionExecutionStatus("GetBoundProfilePackage", getBoundProfilePackageResp);

        GetBoundProfilePackageResponse getBoundProfilePackageResponse = getBoundProfilePackageResp.getResponse();
        Log.debug(TAG, "ES9+ <- : " + getBoundProfilePackageResponse);

        return getBoundProfilePackageResponse;
    }

    public CancelSessionResponseEs9 cancelSession(CancelSessionRequestEs9 cancelSessionRequest) throws Exception {
        ensureSmdpAddressIsAvailable();

        Log.debug(TAG, "ES9+ -> : " + cancelSessionRequest);

        CancelSessionReq cancelSessionReq = new CancelSessionReq();
        cancelSessionReq.setRequest(cancelSessionRequest);

        HttpResponse httpResponse = httpsClient.sendRequest(GS.toJson(cancelSessionReq), smdpAddress, CANCEL_SESSION_PATH, true);
        checkHttpStatusCode("CancelSession", httpResponse, HttpsURLConnection.HTTP_OK);

        CancelSessionResp cancelSessionResp = GS.fromJson(httpResponse.getContent(), CancelSessionResp.class);
        checkFunctionExecutionStatus("CancelSession", cancelSessionResp);

        CancelSessionResponseEs9 cancelSessionResponseEs9 = cancelSessionResp.getResponse();
        Log.debug(TAG, "ES9+ <- : " + cancelSessionResponseEs9);

        return cancelSessionResponseEs9;
    }

    public void handleNotification(PendingNotification pendingNotification) throws Exception {
        ensureSmdpAddressIsAvailable();

        Log.debug(TAG, "ES9+ -> : " + pendingNotification);

        HandleNotificationReq handleNotificationReq = new HandleNotificationReq();
        handleNotificationReq.setRequest(pendingNotification);

        HttpResponse httpResponse = httpsClient.sendRequest(GS.toJson(handleNotificationReq), smdpAddress, HANDLE_NOTIFICATION_PATH, true);
        // No content for response

        checkHttpStatusCode("HandleNotification", httpResponse, HttpsURLConnection.HTTP_NO_CONTENT);
    }

    public FunctionExecutionStatus getFunctionExecutionStatus() {
        return lastFunctionExecutionStatus;
    }

    private void ensureSmdpAddressIsAvailable() throws Exception {
        if(smdpAddress == null) {
            Log.error(TAG, "SM-DP+ address is not available.");
            throw new Exception("SM-DP+ address is not available.");
        }
    }

    private void checkHttpStatusCode(String functionName, HttpResponse httpResponse, int expectedHttpStatusCode) {
        if(httpResponse.getStatusCode() != expectedHttpStatusCode) {
            throw new RuntimeException("Error in " + functionName + ": wrong HTTP status code: " + httpResponse.getStatusCode());
        }
    }

    private void checkFunctionExecutionStatus(String functionName, ResponseMsgBody responseMessage) {
        if((responseMessage.getHeader() != null) &&
                (responseMessage.getHeader().getFunctionExecutionStatus() != null)) {

            this.lastFunctionExecutionStatus = responseMessage.getHeader().getFunctionExecutionStatus();
            String status = lastFunctionExecutionStatus.getStatus();

            if(status.equals(FunctionExecutionStatus.EXECUTION_STATUS_SUCCESS) ||
                    status.equals(FunctionExecutionStatus.EXECUTION_STATUS_WITH_WARNING)) {
                return;
            } else {
                throw new RuntimeException("Error in " + functionName + ": FunctionExecutionStatus: " + lastFunctionExecutionStatus.toString());
            }
        }

        throw new RuntimeException("Error in " + functionName + ": FunctionExecutionStatus not found in response.");
    }
}

Answer: this.es9PlusInterface = profileDownloadSession.getEs9PlusInterface();

<think>
The constructor of CancelSessionWorker initializes the session and the ES10 interface. Since the class also has a field es9PlusInterface, the next logical step is to initialize it from the provided ProfileDownloadSession, mirroring the ES10 initialization. The appropriate call is profileDownloadSession.getEs9PlusInterface().
</think>
this.es9PlusInterface = profileDownloadSession.getEs9PlusInterface();


Input:
import com.alibaba.fastjson2.JSONObject;
import com.ly.ckibana.configure.web.route.HttpRoute;
import com.ly.ckibana.constants.Constants;
import com.ly.ckibana.model.compute.aggregation.bucket.TermsBucket;
import com.ly.ckibana.model.compute.indexpattern.IndexPattern;
import com.ly.ckibana.model.enums.AggType;
import com.ly.ckibana.model.exception.FallbackToEsException;
import com.ly.ckibana.model.request.CkRequestContext;
import com.ly.ckibana.model.request.ProxyConfig;
import com.ly.ckibana.model.request.RequestContext;
import com.ly.ckibana.model.response.Response;
import com.ly.ckibana.parser.ParamParser;
import com.ly.ckibana.parser.ResultParser;
import com.ly.ckibana.service.CkService;
import com.ly.ckibana.service.EsClientUtil;
import com.ly.ckibana.strategy.aggs.Aggregation;
import com.ly.ckibana.util.JSONUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

Context:
src/main/java/com/ly/ckibana/handlers/SearchHandler.java
/*
 * Copyright (c) 2023 LY.com All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.ly.ckibana.handlers;

@SuppressWarnings("rawtypes")
@Component
public class SearchHandler extends BaseHandler {

    @Resource
    private ParamParser paramParser;

    @Resource
    private CkService ckService;

    @Resource
    private ResultParser resultParseService;

    @Override
    public List<HttpRoute> routes() {
        return List.of(
                HttpRoute.newRoute().path("/_search").methods(HttpMethod.GET, HttpMethod.POST),
                HttpRoute.newRoute().path("/{index}/_search").methods(HttpMethod.GET, HttpMethod.POST),
                HttpRoute.newRoute().path("/{index}/{type}/_search").methods(HttpMethod.GET, HttpMethod.POST)
        );
    }

    @SuppressWarnings("unchecked")
    @Override
    public String doHandle(RequestContext context) throws Exception {
        String index = context.getIndex();
        // es6.x 查询索引列表时，传入index默认为 * 或 *:*，此时解析请求体，判断是否查询索引列表，如果是的话，就查询ck和es，merge后返回
        if (StringUtils.isEmpty(index)) {
            throw new FallbackToEsException();
        }
        if (!index.equals(Constants.MATCH_ALL) && !context.isCkIndex()) {
            throw new FallbackToEsException();
        }
        JSONObject searchQuery = JSONUtils.deserialize(context.getRequestInfo().getRequestBody(), JSONObject.class);

src/main/java/com/ly/ckibana/model/compute/aggregation/bucket/TermsBucket.java
@EqualsAndHashCode(callSuper = true)
@Data
public class TermsBucket extends Bucket {

}

src/main/java/com/ly/ckibana/model/compute/indexpattern/IndexPattern.java
@Data
public class IndexPattern {

    private String uiIndex;

    private String index;

    private String database;

    private String timeField;
}

src/main/java/com/ly/ckibana/service/CkService.java
@Slf4j
@Service
public class CkService {

    public static final int SOCKET_TIMEOUT = 120000;

    public static final int CONNECTION_TIMEOUT = 10000;

    public static final int SLOW_THREAD = 10000;

    @Resource
    private ProxyConfigLoader proxyConfigLoader;

    @Resource
    private CkResultCacheService ckResultCacheService;

    @Resource
    private BlackSqlService blackSqlService;

    @Resource
    private SqlMonitorService sqlMonitorService;

    public static String getJdbcUrl(String urlTemplate, String database) {
        return String.format("jdbc:clickhouse://%s/%s", urlTemplate, database);
    }

    public static BalancedClickhouseDataSource initDatasource(CkProperty ckProperty) {
        ClickHouseProperties props = new ClickHouseProperties();
        props.setUseServerTimeZone(false);
        props.setUseTimeZone("Asia/Shanghai");
        props.setSocketTimeout(SOCKET_TIMEOUT);
        props.setConnectionTimeout(CONNECTION_TIMEOUT);
        props.setUser(ckProperty.getUser());
        props.setPassword(ckProperty.getPass());
        String jdbcUrl = String.format("jdbc:clickhouse://%s/%s", ckProperty.getUrl(), ckProperty.getDefaultCkDatabase());
        return new BalancedClickhouseDataSource(jdbcUrl, props);
    }

    /**
     * 获取jdbc,不同集群获取的地址不同.
     *
     * @param indexPattern 索引模式
     * @return jdbc地址
     */
    public String getJdbcUrl(IndexPattern indexPattern) {
        if (proxyConfigLoader.getKibanaProperty().getProxy().getCk() == null) {
            throw new DataSourceEmptyException("clickhouse数据源为空，请检查配置proxy.ck");
        }
        String ckUrl = proxyConfigLoader.getKibanaProperty().getProxy().getCk().getUrl();
        //基于不同集群，用于读数据的ck
        return getJdbcUrl(ckUrl, indexPattern.getDatabase());
    }

    public ClickHouseConnectionImpl getLimitedConnection(String jdbcUrl) throws SQLException {
        return getConnection(jdbcUrl);
    }

    /**
     * 获取ck连接.
     *
     * @param jdbcUrl jdbc地址
     * @return ck连接
     * @throws SQLException sql异常
     */
    private ClickHouseConnectionImpl getConnection(String jdbcUrl) throws SQLException {
        CkProperty ckProperty = proxyConfigLoader.getKibanaProperty().getProxy().getCk();
        if (ckProperty == null) {
            throw new DataSourceEmptyException("clickhouse数据源为空，请检查配置proxy.ck");
        }
        ClickHouseProperties props = new ClickHouseProperties();
        props.setUseServerTimeZone(false);
        props.setUseTimeZone(ZoneId.systemDefault().getId());
        props.setSocketTimeout(SOCKET_TIMEOUT);
        props.setConnectionTimeout(CONNECTION_TIMEOUT);
        props.setUser(ckProperty.getUser());
        props.setPassword(ckProperty.getPass());
        BalancedClickhouseDataSource dataSource = new BalancedClickhouseDataSource(jdbcUrl, props);
        return (ClickHouseConnectionImpl) dataSource.getConnection();
    }

    public List<String> queryTables(ProxyConfig proxyConfig, String tableName) throws Exception {
        String tableCondition = String.format("name = '%s' ", tableName);
        return queryTablesWithCondition(proxyConfig, tableCondition);
    }

    public List<String> queryAllTables(ProxyConfig proxyConfig) throws Exception {
        return queryTablesWithCondition(proxyConfig, null);
    }

    public List<String> queryTables(ProxyConfig proxyConfig, List<String> tablePrefixes) throws Exception {
        String tableCondition = tablePrefixes.stream().map(name -> String.format("'%s'", name)).collect(Collectors.joining(",", "name in (", ")"));
        return queryTablesWithCondition(proxyConfig, tableCondition);
    }

    private List<String> queryTablesWithCondition(ProxyConfig proxyConfig, String tableCondition) throws Exception {
        String sql = String.format("SELECT name FROM system.tables WHERE database = '%s' ", proxyConfig.getCkDatabase());
        if (StringUtils.isNotEmpty(tableCondition)) {
            sql = String.format("%s AND %s ", sql, tableCondition);
        }
        List<JSONObject> tables = queryData(proxyConfig, sql);
        return tables.stream().map(each -> each.getString("name")).collect(Collectors.toList());
    }

    public Map<String, String> queryColumns(BalancedClickhouseDataSource clickhouseDataSource, String table) throws Exception {
        Map<String, String> result = new HashMap<>();
        String sql = String.format("desc `%s`", table);
        try (ClickHouseConnection connection = clickhouseDataSource.getConnection()) {
            ResultSet results = query(connection, sql);
            ResultSetMetaData metaData = results.getMetaData();
            while (results.next()) {
                Map<String, String> columnMap = new HashMap<>();
                for (int i = 1; i <= metaData.getColumnCount(); i++) {
                    columnMap.put(metaData.getColumnName(i), results.getString(metaData.getColumnName(i)));
                }
                result.put(columnMap.get("name"), columnMap.get("type"));
            }
            return result;
        }
    }

    private List<JSONObject> queryData(CkRequestContext ckRequestContext, String sql) throws Exception {
        String jdbcUrl = getJdbcUrl(ckRequestContext.getIndexPattern());
        try (ClickHouseConnectionImpl connection = getLimitedConnection(jdbcUrl)) {
            return formatObjectResult(query(connection, sql));
        }
    }

    private List<JSONObject> queryData(ProxyConfig proxyConfig, String sql) throws Exception {
        try (ClickHouseConnection connection = proxyConfig.getCkDatasource().getConnection()) {
            return formatObjectResult(query(connection, sql));
        }
    }

    public Pair<List<JSONObject>, Boolean> queryDataWithCacheAndStatus(CkRequestContext ckRequestContext, String sql) throws Exception {
        Pair<List<JSONObject>, Boolean> resultAndStatus = Pair.of(new ArrayList<>(), false);
        Exception exception = null;

        if (ckRequestContext.getTimeRange() != null && blackSqlService.isBlackSql(ckRequestContext.getTimeRange().getDiffMillSeconds(), ckRequestContext.getQuerySqlWithoutTimeRange())) {
            throw new BlackSqlException(ckRequestContext.getQuerySqlWithoutTimeRange() + "在黑名单中, 禁止执行");
        }
        long startTime = System.currentTimeMillis();

        try {
            if (ckResultCacheService.containsKey(sql)) {
                resultAndStatus = Pair.of(ckResultCacheService.get(sql), true);
            } else {
                List<JSONObject> result = queryData(ckRequestContext, sql);
                ckResultCacheService.put(sql, result);
                resultAndStatus = Pair.of(result, false);
            }
        } catch (Exception e) {
            exception = e;
            log.error("query data error, sql:{}, cost:{}", sql,
                    Duration.between(Instant.ofEpochMilli(startTime), Instant.ofEpochMilli(System.currentTimeMillis())).toMillis(), e);
        }

        long endTime = System.currentTimeMillis();
        try {
            long range = ckRequestContext.getTimeRange() == null ? 0L : ckRequestContext.getTimeRange().getDiffMillSeconds();
            sqlMonitorService.recordAsync(range, ckRequestContext.getQuerySqlWithoutTimeRange(), startTime, endTime);
        } catch (Exception e) {
            log.error("[monitor-add][{}ms] sql={}, startTime={}, endTime={}", ckRequestContext.getQuerySqlWithoutTimeRange(),
                    startTime, endTime,
                    Duration.between(Instant.ofEpochMilli(startTime), Instant.ofEpochMilli(endTime)).toMillis());
        }

        if (exception != null) {
            throw exception;
        }
        return resultAndStatus;
    }

    public List<JSONObject> queryDataWithoutCache(CkRequestContext ckRequestContext, String sql) throws Exception {
        return queryData(ckRequestContext, sql);
    }

    /**
     * 查询ck,拦截不同报错.
     */
    private ResultSet query(ClickHouseConnection connection, String sql) throws Exception {
        try {
            long begin = System.currentTimeMillis();
            Statement statement = connection.createStatement();
            ResultSet result = statement.executeQuery(sql);
            long cost = System.currentTimeMillis() - begin;
            if (cost >= SLOW_THREAD) {
                log.warn("[query][slowQuery][{}ms] sql={}", cost, sql);
            } else {
                log.info("[query][{}ms] sql={}", cost, sql);
            }
            return result;
        } catch (ClickHouseException ex) {
            log.error("[query] exception sql: {}", sql, ex);
            if (isResourceExceed(ex.getErrorCode())) {
                throw new ResourceExceedException(ex.getMessage());
            } else if (ex.getErrorCode() == ClickHouseErrorCode.TOO_MUCH_SIMULTANEOUS_QUERIES.code) {
                throw new TooManySimultaneousException(ex.getMessage());
            } else if (ex.getErrorCode() == ClickHouseErrorCode.UNKNOWN_IDENTIFIER.code) {
                String field = ex.getCause().getMessage().split("while processing query")[0];
                field = field.replace("Code: 47, e.displayText() = DB::Exception: Missing columns:", "")
                        .replace("'", "").replace(" ", "");
                throw new UnKnowFieldException(field);
            } else if (ex.getErrorCode() == ClickHouseErrorCode.UNKNOWN_TABLE.code) {
                throw new CKNotSupportException(ex.getMessage());
            } else {
                throw new CkSQLException(ex.getMessage());
            }
        } catch (Exception ex) {
            throw ex;
        }
    }

    /**
     * 检查ck结果行是否超过配置.
     *
     * @param rowCount 行数
     * @throws ResourceExceedException 资源超出异常。
     */
    private void isResultRowCountExceed(Integer rowCount) throws ResourceExceedException {
        if (rowCount >= proxyConfigLoader.getKibanaProperty().getQuery().getMaxResultRow()) {
            throw new ResourceExceedException(String.format("超过代理配置maxResultRow %s", proxyConfigLoader.getKibanaProperty().getQuery().getMaxResultRow()));
        }
    }

    private boolean isResourceExceed(int errorCode) {
        return Arrays.asList(ClickHouseErrorCode.MEMORY_LIMIT_EXCEEDED.code, ClickHouseErrorCode.TOO_MUCH_BYTES.code,
                ClickHouseErrorCode.TOO_MANY_ROWS_OR_BYTES.code, ClickHouseErrorCode.QUOTA_EXPIRED.code).contains(errorCode);
    }

    /**
     * 将ck数据封装为list格式返回.
     */
    private List<JSONObject> formatObjectResult(ResultSet resultSet) throws SQLException, ResourceExceedException {
        List<JSONObject> result = new ArrayList<>();
        ResultSetMetaData metaData = resultSet.getMetaData();
        int columnCount = metaData.getColumnCount();
        while (resultSet.next()) {
            JSONObject rowData = new JSONObject();
            for (int i = 1; i <= columnCount; i++) {
                if (ProxyUtils.isArrayType(metaData.getColumnTypeName(i))) {
                    rowData.put(metaData.getColumnName(i), JSONUtils.convert(resultSet.getArray(i).getArray(), List.class));
                } else {
                    rowData.put(metaData.getColumnName(i), resultSet.getObject(i));
                }
            }
            result.add(rowData);
            isResultRowCountExceed(result.size());
        }
        return result;
    }

}

src/main/java/com/ly/ckibana/model/request/CkRequestContext.java
@Data
public class CkRequestContext {

    private IndexPattern indexPattern;

    private String tableName;

    private Map<String, String> columns = new HashMap<>();

    private int size;

    private String sort;

    /**
     * response hits使用 使用原始字段名.
     */
    private List<DocValue> docValues;

    private List<SortedField> sortingFields;

    private String querySqlWithoutTimeRange;

    private String query;

    /**
     * 聚合参数.
     */
    private List<Aggregation> aggs;

    /**
     * 采样参数.
     */
    private SampleParam sampleParam;
    /**
     * 最大结果条数。若大于或等于此阈值,抛出异常,否则可能引发oom.
     */
    private int maxResultRow;

    /**
     * 时间范围参数.
     */
    private Range timeRange;

    /**
     * 记录耗时使用.
     */
    private long beginTime;

    private String database;

    private String clientIp;

    public CkRequestContext() {
    }

    public CkRequestContext(String clientIp, IndexPattern indexPattern, int maxResultRow) {
        this.clientIp = clientIp;
        this.indexPattern = indexPattern;
        this.maxResultRow = maxResultRow;
        database = indexPattern.getDatabase();
        beginTime = System.currentTimeMillis();
    }

    @Data
    @AllArgsConstructor
    public static class SampleParam {

        /**
         * 优化策略，一个msearch查询第一个的count,确定本次查询的采样参数.
         */
        private long sampleTotalCount;

        /**
         * sampleTotalCount数据量超过这个阈值，才会使用采样查询.
         */
        private int sampleCountMaxThreshold;

    }
}

src/main/java/com/ly/ckibana/model/response/Response.java
@Data
public class Response {

    @JsonProperty("timed_out")
    private boolean timedOut;

    @JsonProperty("_shards")
    private Shards shards = new Shards();

    private Hits hits = new Hits();

    private int took;

    private boolean cache;

    /**
     * 聚合名-{buckets信息}.
     */
    private Map<String, Map<String, Object>> aggregations;

    private Integer status;

    private Object error;

    private List<String> sqls = new ArrayList<>();
}

src/main/java/com/ly/ckibana/util/JSONUtils.java
@Slf4j
public final class JSONUtils {

    /**
     * 序列化.
     */
    public static String serialize(Object o) {
        try {
            ObjectMapper mapper = new ObjectMapper();
            return mapper.writeValueAsString(o);
        } catch (Exception ex) {
            log.error("serialize error", ex);
            return "{}";
        }
    }

    /**
     * 反序列化.
     */
    public static <T> T deserialize(String json, Class<T> type) {
        try {
            return JSON.parseObject(json, type);
        } catch (Exception ex) {
            log.error("deserialize error, json:{}", json, ex);
            throw ex;
        }
    }

    /**
     * 反序列化为list.
     */
    public static <T> List<T> deserializeToList(String json, Class<T> tClass) {
        List<T> result = new ArrayList<>();
        deserialize(json, List.class).stream().forEach(each -> result.add(convert(each, tClass)));
        return result;
    }

    /**
     * list转换.
     */
    public static <T> List<T> deserializeToList(List<?> list, Class<T> tClass) {
        if (list == null) {
            return new ArrayList<>();
        }
        return list.stream().map(o -> convert(o, tClass)).collect(Collectors.toList());
    }

    /**
     * object转换为map.
     */
    public static Map convertToMap(Object o) {
        return convert(o, Map.class);
    }

    /**
     * object转换为其他类型.
     */
    public static <T> T convert(Object o, Class<T> type) {
        try {
            if (o == null) {
                return null;
            }
            String json = JSON.toJSONString(o);
            return JSON.parseObject(json, type);
        } catch (Exception ex) {
            log.error("convert error", ex);
            throw ex;
        }
    }

    /**
     * 拷贝生成同类新对象.
     */
    public static <T> T copy(Object source) {
        Object result = new BeanWrapperImpl(source.getClass()).getWrappedInstance();
        BeanUtils.copyProperties(source, result);
        return (T) result;
    }
}

src/main/java/com/ly/ckibana/model/request/RequestContext.java
@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class RequestContext {

    private HttpServletRequest httpRequest;

    private HttpServletResponse httpResponse;

    private Map<String, String> urlParams;

    private ProxyConfig proxyConfig;

    private RequestInfo requestInfo;

    private String clientIp;

    private String targetUrl;

    private String requestUrl;

    /**
     * 去掉 originalIndex 中的 remote cluster，比如 remote_cluster_name:index_name -> index_name.
     */
    private String index;

    /**
     * url路径中的原始index，可能包含 remote cluster，比如 remote_cluster_name:index_name.
     */
    private String originalIndex;

    /**
     * 是否匹配 proxy config 的黑白名单，属于 ck 索引.
     */
    private boolean ckIndex;

    @Data
    @AllArgsConstructor
    public static class RequestInfo {
        private Map<String, String> params;

        private Header[] headers;

        private String method;

        private String url;

        private String requestBody;
    }
}

src/main/java/com/ly/ckibana/model/exception/FallbackToEsException.java
public class FallbackToEsException extends RuntimeException {

    public FallbackToEsException() {
    }

    public FallbackToEsException(String msg) {
        super(msg);
    }
}

src/main/java/com/ly/ckibana/model/request/ProxyConfig.java
@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
public class ProxyConfig {
    public static final Logger log = LoggerFactory.getLogger(ProxyConfig.class);

    private KibanaItemProperty kibanaItemProperty;

    private RestClient restClient;

    private EsProxyClientConsumer esClientBuffer;

    private BalancedClickhouseDataSource ckDatasource;

    public ProxyConfig(KibanaItemProperty kibanaItemProperty) {
        this.kibanaItemProperty = kibanaItemProperty;
        this.restClient = RestUtils.initEsResClient(kibanaItemProperty.getEs());
        this.esClientBuffer = new EsProxyClientConsumer();
        if (kibanaItemProperty.getCk() != null) {
            this.ckDatasource = CkService.initDatasource(kibanaItemProperty.getCk());
        } else {
            this.ckDatasource = null;
        }
    }

    public String getCkDatabase() {
        if (kibanaItemProperty.getCk() == null) {
            throw new DataSourceEmptyException("clickhouse数据源为空，请检查配置proxy.ck");
        }
        return kibanaItemProperty.getCk().getDefaultCkDatabase();
    }

    public boolean shouldUpdateEsClient(KibanaItemProperty properties) {
        if (properties == null || properties.getEs() == null) {
            return false;
        }
        if (StringUtils.isEmpty(this.kibanaItemProperty.getEs().getHost()) && StringUtils.isEmpty(properties.getEs().getHost())) {
            return false;
        }
        boolean hostDiff = !Objects.equals(this.kibanaItemProperty.getEs().getHost(), properties.getEs().getHost());
        Map<String, String> oldHeaders = this.kibanaItemProperty.getEs().getHeaders();
        Map<String, String> newHeaders = properties.getEs().getHeaders();
        boolean headersDiff;
        if (oldHeaders != null && newHeaders != null) {
            headersDiff = !oldHeaders.toString().equals(newHeaders.toString());
        } else {
            headersDiff = !(oldHeaders == null && newHeaders == null);
        }
        return headersDiff || hostDiff;
    }

    public boolean shouldUpdateCkClient(KibanaItemProperty properties) {
        if (properties == null || properties.getCk() == null) {
            return false;
        }
        CkProperty oldProperty = this.kibanaItemProperty.getCk();
        CkProperty newProperty = properties.getCk();
        if (oldProperty == null && newProperty != null) {
            return true;
        }
        return !Objects.equals(newProperty, oldProperty);
    }

    public boolean isDirectToEs(String index) {
        if (StringUtils.isEmpty(index)) {
            return true;
        }
        if (CollectionUtils.isEmpty(kibanaItemProperty.getBlackIndexList()) && CollectionUtils.isEmpty(kibanaItemProperty.getWhiteIndexList())) {
            return true;
        }
        if (!CollectionUtils.isEmpty(kibanaItemProperty.getBlackIndexList())) {
            for (String each : kibanaItemProperty.getBlackIndexList()) {
                if (index.equals(each)) {
                    return true;
                }
            }
        }
        if (!CollectionUtils.isEmpty(kibanaItemProperty.getWhiteIndexList())) {
            for (String each : kibanaItemProperty.getWhiteIndexList()) {
                if (index.equals(each)) {
                    return false;
                }
            }
        }
        return true;
    }

    public IndexPattern buildIndexPattern(String originIndex) {
        String index = ProxyUtils.trimRemoteCluster(originIndex);
        return buildIndexPattern(index, index);
    }

    public IndexPattern buildIndexPattern(String uiIndex, String index) {
        IndexPattern indexPattern = new IndexPattern();
        String database = getCkDatabase();
        indexPattern.setUiIndex(uiIndex);
        indexPattern.setIndex(index);
        indexPattern.setDatabase(database);
        return indexPattern;
    }
}

src/main/java/com/ly/ckibana/parser/ParamParser.java
@Slf4j
@Service
public class ParamParser {

    private static final String KEYED = "keyed";

    private static final String FIELD = "field";

    @Resource
    private ProxyConfigLoader proxyConfigLoader;

    @Resource
    private CkService ckService;

    @Resource
    private List<Aggregation> aggStrategyList;

    @Resource
    private FiltersAggsStrategyHelpler filtersAggsStrategyHelpler;

    @Setter
    private Map<AggType, Aggregation> aggStrategyMap;

    @PostConstruct
    public void init() {
        aggStrategyMap = new HashMap<>(aggStrategyList.size());
        aggStrategyList.forEach(each -> aggStrategyMap.put(each.getAggType(), each));
    }

    /**
     * 基于入口参数，解析indexPattern信息.
     * 远程集群名（数据库__集群业务名称):索引名 如businesslog__log:anquan_dns-*
     * 兼容原有es业务，需要default库名
     */
    public IndexPattern buildIndexPattern(RequestContext context) {
        return context.getProxyConfig().buildIndexPattern(context.getOriginalIndex(), context.getIndex());
    }

    /**
     * 是否需要采样，部分index采样。默认不采样.
     *
     * @param uiIndex uiIndex
     * @return 是否需要采样
     */
    public boolean checkIfNeedSampleByIndex(String uiIndex) {
        QueryProperty queryProperty = proxyConfigLoader.getKibanaProperty().getQuery();
        if (queryProperty == null) {
            return false;
        }
        List<String> sampleIndexPatterns = queryProperty.getSampleIndexPatterns();
        if (CollectionUtils.isEmpty(sampleIndexPatterns)) {
            return false;
        }
        for (String each : sampleIndexPatterns) {
            if (uiIndex.equals(each)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 将查询语句转换为对应的agg聚合策略.
     */
    public List<Aggregation> parseAggs(int depth, CkRequestContext ckRequestContext, JSONObject searchQuery) {
        List<Aggregation> result = null;
        if (searchQuery.containsKey(Constants.AGGS)) {
            result = new ArrayList<>();
            JSONObject aggs = searchQuery.getJSONObject(Constants.AGGS);
            for (String aggsKey : aggs.keySet()) {
                List<Aggregation> subAggs = null;
                JSONObject aggsSettings = aggs.getJSONObject(aggsKey);
                if (aggsSettings.containsKey(Constants.AGGS)) {
                    subAggs = parseAggs(depth + 1, ckRequestContext, aggsSettings);
                }
                List<Aggregation> currentAggs = doParseAggs(depth, aggsKey, ckRequestContext, aggsSettings, subAggs);
                if (currentAggs.isEmpty()) {
                    continue;
                }
                if (aggsSettings.containsKey(Constants.AGGS) && CollectionUtils.isNotEmpty(subAggs)) {
                    for (Aggregation currentAgg : currentAggs) {
                        currentAgg.setSubAggs(subAggs);
                    }
                }
                result.addAll(currentAggs);
            }
        }
        return result;
    }

    /**
     * 基于agg类型解析出对应的object.
     */
    private List<Aggregation> doParseAggs(int depth, String aggName, CkRequestContext ckRequestContext, JSONObject parentSetting, List<Aggregation> subAggs) {
        List<Aggregation> aggs = new ArrayList<>();
        AggsParam baseAggsParam = new AggsParam(aggName, depth, ckRequestContext.getQuery(), ckRequestContext.getColumns(), subAggs);
        for (String each : parentSetting.keySet()) {
            if (Constants.AGGS.equals(each)) {
                continue;
            }
            AggsParam aggsParam = buildAggParam(baseAggsParam, each, parentSetting.getJSONObject(each));
            AggType type = aggsParam.getAggType();
            Aggregation aggregation = aggStrategyMap.get(type);
            if (null == aggregation) {
                throw new UnSupportAggsTypeException(each, AggType.getAggTypes());
            }
            if (AggType.FILTERS.equals(type)) {
                aggs.add(filtersAggsStrategyHelpler.generate(aggsParam, ckRequestContext));
            } else {
                aggs.add(aggregation.generate(aggsParam));
            }
        }
        return aggs;
    }

    private AggsParam buildAggParam(AggsParam baseAggsParam, String aggType, JSONObject aggSetting) {
        AggsParam result = JSONUtils.copy(baseAggsParam);
        String aggField = aggSetting.getString(FIELD);
        Map<String, String> supportColumns = baseAggsParam.getColumns();
        if (!validAggField(aggType, aggField, supportColumns)) {
            throw new UnSupportAggsTypeException(aggType.toUpperCase(), aggField, AggType.getAggTypes());
        }
        try {
            result.setAggType(AggType.valueOf(aggType.toUpperCase()));
            result.setAggBucketsName(getAggBucketsNameByAggType(result));
        } catch (Exception e) {
            throw new UnSupportAggsTypeException(aggType.toUpperCase(), aggField, AggType.getAggTypes());
        }
        String fieldType = supportColumns.get(aggField);
        result.setAggSetting(aggSetting);
        result.setField(aggField);
        result.setKeyed(aggSetting.getBooleanValue(KEYED));
        result.setFieldType(fieldType);
        return result;
    }

    private AggBucketsName getAggBucketsNameByAggType(AggsParam result) {
        return (AggType.PERCENTILES.equals(result.getAggType()) || AggType.PERCENTILE_RANKS.equals(result.getAggType()))
                ? AggBucketsName.VALUES : AggBucketsName.BUCKETS;
    }

    private boolean validAggField(String aggType, String aggField, Map<String, String> supportColumns) {
        return isFiltersAgg(aggType) || !isMissAggField(aggField) && !isMissAggFieldColumn(aggField, supportColumns);
    }

    /**
     * 是否ck列中缺失aggField字段.
     *
     * @param aggField       aggField
     * @param supportColumns supportColumns
     * @return 是否ck列中缺失的字段
     */
    private boolean isMissAggFieldColumn(String aggField, Map<String, String> supportColumns) {
        return !Constants.ES_INDEX_QUERY_FIELD.equals(aggField) && !supportColumns.containsKey(aggField);
    }

    /**
     * 是否用户参数缺失aggField.
     *
     * @param aggField aggField
     * @return boolean
     */
    private boolean isMissAggField(String aggField) {
        return StringUtils.isEmpty(aggField);
    }

    /**
     * 是否为filters聚合.
     *
     * @param aggType aggType
     * @return boolean
     */
    private boolean isFiltersAgg(String aggType) {
        return AggType.FILTERS.name().toLowerCase().equals(aggType);
    }

    /**
     * 查询列信息.
     */
    public Map<String, String> queryTableColumns(BalancedClickhouseDataSource clickhouseDataSource, String tableName) throws Exception {
        return ckService.queryColumns(clickhouseDataSource, tableName);
    }

    /**
     * 如果需要走 cache，cache 返回不为空再请求es.
     */
    public Map<String, String> queryColumnsFromCache(Map<String, Map<String, String>> cache, String tableName) {
        Map<String, String> result = null;
        //不为空不为*的table名称才需要查询列名
        if (Strings.isNullOrEmpty(tableName) || ProxyUtils.matchAllIndex(tableName)) {
            return new HashMap<>();
        }
        if (cache != null && cache.containsKey(tableName)) {
            result = cache.get(tableName);
        }
        return result;
    }

    /**
     * 基于indexPattern，查询其下的字段，包括名称和类型.
     *
     * @param requestContext       请求上下文
     * @param indexPattern         当前index pattern
     * @param isForSelectTimeField 是:代表为创建index pattern时间时选择时间字段列表场景，需要额外类型转换（规则参见ProxyUtils.isTimeFieldOptionByName）
     *                             否:代表查询index pattern字段列表。无需额外类型转换
     */
    public Map<String, JSONObject> queryIndexPatternFields(RequestContext requestContext, IndexPattern indexPattern,
                                                           boolean isForSelectTimeField) throws Exception {
        Map<String, JSONObject> result = new HashMap<>();
        String tableName = indexPattern.getIndex();
        Map<String, String> columns = ckService.queryColumns(requestContext.getProxyConfig().getCkDatasource(), tableName);
        //特殊查询字段不存在，转为真实对应的ck字段
        for (Map.Entry<String, String> each : columns.entrySet()) {
            String ckName = each.getKey();
            String ckType = each.getValue();
            //当isForSelectTimeField为true情况，若indexPattern已经设置时间字段，则非时间字段按原始类型返回即可。
            String esType = ProxyUtils.convertCkTypeToEsType(ckName, ckType,
                    isNotIndexPatternTimeField(indexPattern, ckName) ? Boolean.FALSE : isForSelectTimeField);
            JSONObject jsonObjectType = new JSONObject();
            jsonObjectType.put(esType, new IndexPatternFields(ckName, esType));
            result.put(ckName, jsonObjectType);
        }
        return result;
    }

    /**
     * 是否命中已经被设置为时间字段的字段名.
     *
     * @param indexPattern 当前index pattern
     * @param ckName       ck字段名
     * @return boolean
     */
    private boolean isNotIndexPatternTimeField(IndexPattern indexPattern, String ckName) {
        return StringUtils.isNotBlank(indexPattern.getTimeField()) && !ckName.equals(indexPattern.getTimeField());
    }

    /**
     * 计算用户选择的时间框间隔ms.
     *
     * @param timeRange range时间范围
     */
    public long getTimeIntervalMillSeconds(Range timeRange) {
        return timeRange == null ? 0L : timeRange.getDiffMillSeconds();
    }

    /**
     * 时间参数round.
     * 2分钟内数据不round, 默认round 10s
     * 返回ui
     */
    public Range getTimeRangeAfterRound(CkRequestContext ckRequestContext) {
        Range result = ckRequestContext.getTimeRange();
        KibanaItemProperty kibanaItemProperty = proxyConfigLoader.getKibanaProperty().getProxy();
        boolean isRoundAble = getTimeIntervalMillSeconds(result) > kibanaItemProperty.getRoundAbleMinPeriod();
        if (isRoundAble) {
            int realRound = kibanaItemProperty.getRound() > 0 ? kibanaItemProperty.getRound() : Constants.ROUND_SECOND;
            result.setHigh(DateUtils.roundToMSecond(Math.min(System.currentTimeMillis(), (Long) result.getHigh()), realRound));
            result.setLow(DateUtils.roundToMSecond((Long) result.getLow(), realRound));
        }
        return result;
    }

    /**
     * 查询代理配置MaxResultRow.
     *
     * @return MaxResultRow
     */
    public int getMaxResultRow() {
        return this.proxyConfigLoader.getKibanaProperty().getQuery().getMaxResultRow();
    }
}

Answer: IndexPattern indexPattern = paramParser.buildIndexPattern(context);

<think>
After deserializing the request body into a JSONObject named searchQuery, the handler will need to determine the ClickHouse index pattern for the request. The ParamParser provides a buildIndexPattern method that takes the RequestContext. So the next logical line creates an IndexPattern using that parser.
</think>
IndexPattern indexPattern = paramParser.buildIndexPattern(context);


Input:
import com.google.gson.Gson;
import de.morihofi.acmeserver.certificate.acme.api.Provisioner;
import de.morihofi.acmeserver.certificate.acme.api.abstractclass.AbstractAcmeEndpoint;
import de.morihofi.acmeserver.certificate.acme.api.endpoints.objects.Identifier;
import de.morihofi.acmeserver.certificate.acme.api.endpoints.order.objects.ACMEOrderResponse;
import de.morihofi.acmeserver.certificate.acme.api.endpoints.order.objects.FinalizeOrderRequestPayload;
import de.morihofi.acmeserver.certificate.objects.ACMERequestBody;
import de.morihofi.acmeserver.database.Database;
import de.morihofi.acmeserver.database.objects.ACMEAccount;
import de.morihofi.acmeserver.database.objects.ACMEIdentifier;
import de.morihofi.acmeserver.exception.exceptions.ACMEBadCsrException;
import de.morihofi.acmeserver.tools.base64.Base64Tools;
import de.morihofi.acmeserver.tools.certificate.PemUtil;
import de.morihofi.acmeserver.tools.certificate.dataExtractor.CsrDataExtractor;
import de.morihofi.acmeserver.tools.crypto.Crypto;
import de.morihofi.acmeserver.tools.dateAndTime.DateTools;
import de.morihofi.acmeserver.tools.certificate.generator.ServerCertificateGenerator;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.javalin.http.Context;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.util.io.pem.PemObject;
import java.math.BigInteger;
import java.security.cert.X509Certificate;
import java.sql.Timestamp;
import java.util.List;
import java.util.stream.Collectors;

Context:
src/main/java/de/morihofi/acmeserver/certificate/acme/api/endpoints/order/FinalizeOrderEndpoint.java
package de.morihofi.acmeserver.certificate.acme.api.endpoints.order;

public class FinalizeOrderEndpoint extends AbstractAcmeEndpoint {

    /**
     * Logger
     */
    public final Logger log = LogManager.getLogger(getClass());

    /**
     * ACME Endpoint for finalize an order
     *
     * @param provisioner Provisioner instance
     */
    @SuppressFBWarnings("EI_EXPOSE_REP2")
    public FinalizeOrderEndpoint(Provisioner provisioner) {
        super(provisioner);
    }

    @Override
    public void handleRequest(Context ctx, Provisioner provisioner, Gson gson, ACMERequestBody acmeRequestBody) throws Exception {
        String orderId = ctx.pathParam("orderId");

        ACMEAccount account = Database.getAccountByOrderId(orderId);
        // Check signature and nonce
        assert account != null;
        performSignatureAndNonceCheck(ctx, account, acmeRequestBody);

        //After check parse payload
        FinalizeOrderRequestPayload reqBodyPayloadObj = gson.fromJson(acmeRequestBody.getDecodedPayload(), FinalizeOrderRequestPayload.class);
        String csr = reqBodyPayloadObj.getCsr();

        List<String> csrDomainNames = CsrDataExtractor.getDomainsFromCSR(csr);
        if (csrDomainNames.isEmpty()) {

src/main/java/de/morihofi/acmeserver/certificate/objects/ACMERequestBody.java
public class ACMERequestBody {
    @SerializedName("protected")
    private String protectedHeader;
    @SerializedName("payload")
    private String payload;
    @SerializedName("signature")
    private String signature;

    /**
     * Retrieves the decoded value of the protected header.
     * The protected header is Base64-encoded and contains information about the request and the signature algorithm.
     *
     * @return The decoded protected header as a {@code String}.
     */
    public String getDecodedProtected() {
        return Base64Tools.decodeBase64(protectedHeader);
    }

    /**
     * Retrieves the raw, Base64-encoded value of the protected header.
     *
     * @return The Base64-encoded protected header as a {@code String}.
     */
    public String getProtected() {
        return protectedHeader;
    }

    /**
     * Retrieves the raw, Base64-encoded payload of the ACME request.
     * The payload contains the actual data of the request.
     *
     * @return The Base64-encoded payload as a {@code String}.
     */
    public String getPayload() {
        return payload;
    }

    /**
     * Retrieves the decoded value of the payload.
     * The payload is Base64-encoded and contains the actual data of the request.
     *
     * @return The decoded payload as a {@code String}.
     */
    public String getDecodedPayload() {
        return Base64Tools.decodeBase64(payload);
    }

    /**
     * Retrieves the raw, Base64-encoded signature of the ACME request.
     * The signature verifies the authenticity and integrity of the protected header and payload.
     *
     * @return The Base64-encoded signature as a {@code String}.
     */
    public String getSignature() {
        return signature;
    }
}

src/main/java/de/morihofi/acmeserver/tools/certificate/dataExtractor/CsrDataExtractor.java
public class CsrDataExtractor {

    /**
     * Extracts domain names from a Certificate Signing Request (CSR) in PEM format.
     *
     * @param csr The Certificate Signing Request in PEM format.
     * @return A list of domain names (Subject Alternative Names) extracted from the CSR.
     * @throws IOException If an error occurs while processing the CSR.
     */
    public static List<String> getDomainsFromCSR(String csr) throws IOException {
        byte[] csrBytes = Base64Tools.decodeBase64URLAsBytes(csr);
        PKCS10CertificationRequest certRequest = new PKCS10CertificationRequest(csrBytes);

        List<String> domainList = new ArrayList<>();

        // Extract the SAN extension
        Extension sanExtension = certRequest.getRequestedExtensions().getExtension(Extension.subjectAlternativeName);
        if (sanExtension != null) {
            GeneralNames san = GeneralNames.getInstance(sanExtension.getParsedValue());
            GeneralName[] names = san.getNames();

            // Loop through all names in the SAN
            for (GeneralName name : names) {
                if (name.getTagNo() == GeneralName.dNSName) {
                    String dnsName = name.getName().toString();
                    domainList.add(dnsName);
                }
            }
        }

        return domainList;
    }
}

src/main/java/de/morihofi/acmeserver/certificate/acme/api/Provisioner.java
public class Provisioner {

    /**
     * Get the ACME Server URL, reachable from other Hosts
     *
     * @return Full url (including HTTPS prefix) and port to this server
     */
    public String getApiURL() {
        return "https://" + Main.appConfig.getServer().getDnsName() + ":" + Main.appConfig.getServer().getPorts().getHttps() + "/" + provisionerName;
    }

    /**
     * Retrieves the server URL constructed from the application's configuration.
     * This method combines the DNS name and HTTPS port specified in the app configuration
     * to form the complete server URL.
     *
     * @return a String representing the full HTTPS URL of the server
     */
    public String getServerURL() {
        return "https://" + Main.appConfig.getServer().getDnsName() + ":" + Main.appConfig.getServer().getPorts().getHttps();
    }

    /**
     * The name of the provisioner. Immutable after initial assignment.
     */
    private final String provisionerName;

    /**
     * Configuration for ACME (Automated Certificate Management Environment) metadata.
     * This configuration can be changed during the lifecycle of the object.
     */
    private MetadataConfig acmeMetadataConfig;

    /**
     * Configuration for the expiration of generated certificates.
     * This configuration can be adjusted as needed.
     */
    private CertificateExpiration generatedCertificateExpiration;

    /**
     * Flag indicating whether wildcards are allowed in certificate requests.
     * Can be toggled to enable or disable wildcard support.
     */
    private boolean wildcardAllowed;

    /**
     * Manager for cryptographic store operations.
     * Provides functionality for managing cryptographic elements such as keys and certificates.
     */
    private final CryptoStoreManager cryptoStoreManager;

    /**
     * Configuration for domain name restrictions.
     * Defines the constraints and rules for domain names in the context of this provisioner.
     */
    private final DomainNameRestrictionConfig domainNameRestriction;

    /**
     * Constructs a new Provisioner object.
     * This constructor initializes the Provisioner with the specified settings and configurations.
     * It sets up various aspects like the provisioner's name, ACME metadata configuration, certificate expiration settings,
     * domain name restrictions, wildcard allowance, and the crypto store manager.
     *
     * @param provisionerName                The name of the provisioner.
     * @param acmeMetadataConfig             The ACME metadata configuration.
     * @param generatedCertificateExpiration The settings for the expiration of generated certificates.
     * @param domainNameRestriction          The configuration for domain name restrictions.
     * @param wildcardAllowed                A boolean value indicating whether wildcards are allowed.
     * @param cryptoStoreManager             The manager for cryptographic store operations.
     */
    @SuppressFBWarnings("EI_EXPOSE_REP2")
    public Provisioner(String provisionerName, MetadataConfig acmeMetadataConfig, CertificateExpiration generatedCertificateExpiration, DomainNameRestrictionConfig domainNameRestriction, boolean wildcardAllowed, CryptoStoreManager cryptoStoreManager) {
        this.provisionerName = provisionerName;
        this.acmeMetadataConfig = acmeMetadataConfig;
        this.generatedCertificateExpiration = generatedCertificateExpiration;
        this.domainNameRestriction = domainNameRestriction;
        this.wildcardAllowed = wildcardAllowed;
        this.cryptoStoreManager = cryptoStoreManager;
    }

    /**
     * Checks if wildcard is allowed in the configuration.
     * This method returns a boolean indicating whether wildcard usage is permitted.
     *
     * @return {@code true} if wildcard is allowed, otherwise {@code false}.
     */
    public boolean isWildcardAllowed() {
        return wildcardAllowed;
    }

    /**
     * Sets the wildcard allowance status.
     * This method allows enabling or disabling the usage of wildcards.
     *
     * @param wildcardAllowed A boolean value to set the wildcard allowance status.
     */
    public void setWildcardAllowed(boolean wildcardAllowed) {
        this.wildcardAllowed = wildcardAllowed;
    }

    /**
     * Retrieves the domain name restriction configuration.
     * This configuration dictates the constraints on domain names.
     * Note: The returned object is a direct reference and any changes will affect the original object.
     *
     * @return The {@link DomainNameRestrictionConfig} object representing domain name restrictions.
     */
    @SuppressFBWarnings("EI_EXPOSE_REP")
    public DomainNameRestrictionConfig getDomainNameRestriction() {
        return domainNameRestriction;
    }

    /**
     * Retrieves the configuration for generated certificate expiration.
     * This object provides details about the expiration settings for generated certificates.
     * Note: The returned object is a direct reference and any changes will affect the original object.
     *
     * @return The {@link CertificateExpiration} object representing the expiration settings.
     */
    @SuppressFBWarnings("EI_EXPOSE_REP")
    public CertificateExpiration getGeneratedCertificateExpiration() {
        return generatedCertificateExpiration;
    }

    /**
     * Sets the configuration for generated certificate expiration.
     * This method allows setting the expiration details for newly generated certificates.
     * Note: The input object is used as a direct reference, and changes to it will reflect in the system.
     *
     * @param generatedCertificateExpiration The {@link CertificateExpiration} object to set.
     */
    @SuppressFBWarnings("EI_EXPOSE_REP2")
    public void setGeneratedCertificateExpiration(CertificateExpiration generatedCertificateExpiration) {
        this.generatedCertificateExpiration = generatedCertificateExpiration;
    }

    /**
     * Retrieves the name of the provisioner.
     * This method returns the name assigned to the provisioner, which is
     * used in various other operations within the system.
     *
     * @return A {@code String} representing the name of the provisioner.
     */
    public String getProvisionerName() {
        return provisionerName;
    }

    /**
     * Constructs and returns the path for the Certificate Revocation List (CRL).
     * This method creates a path string for the CRL using the provisioner's name.
     * The path is typically used to access or store the CRL file in a specific directory structure.
     *
     * @return A {@code String} representing the path for the CRL file, specific to the provisioner.
     */
    public String getCrlPath() {
        return "/crl/" + getProvisionerName() + ".crl";
    }

    /**
     * Constructs and returns the path for the Online Certificate Status Protocol (OCSP) service.
     * This method generates the path used to access the OCSP service, incorporating the provisioner's name.
     * The path is usually part of the URL used to interact with the OCSP service.
     *
     * @return A {@code String} representing the OCSP service path, associated with the provisioner.
     */
    public String getOcspPath() {
        return "/" + getProvisionerName() + "/ocsp";
    }

    /**
     * Retrieves the intermediate Certificate Authority (CA) certificate.
     * This method fetches the X.509 certificate associated with the intermediate CA
     * from the KeyStore. It uses a specific alias to locate the certificate.
     *
     * @return The intermediate CA's {@link X509Certificate}.
     * @throws KeyStoreException If an error occurs while accessing the KeyStore.
     */
    public X509Certificate getIntermediateCaCertificate() throws KeyStoreException {

        String alias = CryptoStoreManager.getKeyStoreAliasForProvisionerIntermediate(provisionerName);
        KeyStore keyStore = cryptoStoreManager.getKeyStore();
        return (X509Certificate) keyStore.getCertificate(alias);
    }

    /**
     * Retrieves the KeyPair associated with the intermediate Certificate Authority (CA).
     * This method fetches both the public and private keys for the intermediate CA from the KeyStore.
     * It utilizes a specific alias to locate these keys.
     *
     * @return A {@link KeyPair} consisting of the intermediate CA's public and private keys.
     * @throws KeyStoreException         If an error occurs while accessing the KeyStore.
     * @throws UnrecoverableKeyException If the key cannot be recovered (typically due to an incorrect password or corruption).
     * @throws NoSuchAlgorithmException  If the algorithm for recovering the key is not available.
     */
    public KeyPair getIntermediateCaKeyPair() throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException {

        String alias = CryptoStoreManager.getKeyStoreAliasForProvisionerIntermediate(provisionerName);

        KeyStore keyStore = cryptoStoreManager.getKeyStore();

        return new KeyPair(
                keyStore.getCertificate(alias).getPublicKey(),
                (PrivateKey) keyStore.getKey(alias, "".toCharArray())
        );
    }

    public MetadataConfig getAcmeMetadataConfig() {
        return acmeMetadataConfig;
    }

    @SuppressFBWarnings("EI_EXPOSE_REP2")
    public void setAcmeMetadataConfig(MetadataConfig acmeMetadataConfig) {
        this.acmeMetadataConfig = acmeMetadataConfig;
    }

    /**
     * Returns the full OCSP (Online Certificate Status Protocol) URL.
     * This method combines the server URL with the OCSP path to construct
     * the full OCSP URL.
     *
     * @return A {@code String} representing the full OCSP URL.
     */
    public String getFullOcspUrl() {
        return getServerURL() + getOcspPath();
    }

    /**
     * Returns the full CRL (Certificate Revocation List) URL.
     * This method concatenates the server URL with the CRL path to
     * create the full CRL URL.
     *
     * @return A {@code String} representing the full CRL URL.
     */
    public String getFullCrlUrl() {
        return getServerURL() + getCrlPath();
    }

    /**
     * Retrieves the instance of the CryptoStoreManager.
     * This manager is responsible for managing cryptographic elements
     * such as keys and certificates.
     *
     * @return The {@code CryptoStoreManager} instance currently in use.
     */
    public CryptoStoreManager getCryptoStoreManager() {
        return cryptoStoreManager;
    }

}

src/main/java/de/morihofi/acmeserver/tools/dateAndTime/DateTools.java
public class DateTools {

    private DateTools(){}

    /**
     * Formats a {@link Date} object as a string in the ACME date format.
     *
     * @param date The {@link Date} object to be formatted.
     * @return A string representing the formatted date in the "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" format in UTC time zone.
     */
    public static String formatDateForACME(Date date) {
        // Set the date format
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));

        // Format the date and return it as a string
        return dateFormat.format(date);
    }
}

src/main/java/de/morihofi/acmeserver/certificate/acme/api/abstractclass/AbstractAcmeEndpoint.java
public abstract class AbstractAcmeEndpoint implements Handler {

    /**
     * Instance for accessing the current provisioner
     */
    private final Provisioner provisioner;

    /**
     * Gson for JSON to POJO and POJO to JSON conversion
     */
    private final Gson gson;

    public AbstractAcmeEndpoint(Provisioner provisioner) {
        this.provisioner = provisioner;
        this.gson = new Gson();
    }

    public Provisioner getProvisioner() {
        return provisioner;
    }

    public Gson getGson() {
        return gson;
    }

    @Override
    public void handle(@NotNull Context ctx) throws Exception {
        ACMERequestBody acmeRequestBody = gson.fromJson(ctx.body(), ACMERequestBody.class);

        handleRequest(ctx, provisioner, gson, acmeRequestBody);
    }

    public abstract void handleRequest(Context ctx, Provisioner provisioner, Gson gson, ACMERequestBody acmeRequestBody) throws Exception;

    public void performSignatureAndNonceCheck(Context ctx, String accountId, ACMERequestBody acmeRequestBody) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeySpecException, InvalidKeyException, NoSuchProviderException {
        // Check signature and nonce
        SignatureCheck.checkSignature(ctx, accountId, gson);
        NonceManager.checkNonceFromDecodedProtected(acmeRequestBody.getDecodedProtected());
    }

    public void performSignatureAndNonceCheck(Context ctx, ACMEAccount account, ACMERequestBody acmeRequestBody) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeySpecException, InvalidKeyException, NoSuchProviderException {
        performSignatureAndNonceCheck(ctx, account.getAccountId(), acmeRequestBody);
    }
}

src/main/java/de/morihofi/acmeserver/certificate/acme/api/endpoints/objects/Identifier.java
public class Identifier {

    /**
     * Type of the DNS identifier, mostly dns
     */
    private String type;

    /**
     * Value of the identifier, so it is the DNS Name
     */
    private String value;

    /**
     * Constructs a new Identifier with specified type and value.
     * This constructor initializes the Identifier with a specific type (e.g., 'dns') and its corresponding value.
     *
     * @param type The type of the identifier (e.g., 'dns').
     * @param value The value of the identifier (e.g., a DNS name).
     */
    public Identifier(String type, String value) {
        this.type = type;
        this.value = value;
    }

    /**
     * Default constructor for Identifier.
     * Constructs an Identifier with default values for type and value.
     */
    public Identifier() {
    }

    /**
     * Retrieves the type of this identifier.
     * The type usually indicates the category of the identifier (e.g., 'dns' for DNS names).
     *
     * @return The type of the identifier as a {@code String}.
     */
    public String getType() {
        return type;
    }

    /**
     * Sets the type of this identifier.
     * This method allows changing the type of the identifier.
     *
     * @param type The new type of the identifier as a {@code String}.
     */
    public void setType(String type) {
        this.type = type;
    }

    /**
     * Retrieves the value of this identifier.
     * The value is specific to the type of the identifier (e.g., a DNS name for type 'dns').
     *
     * @return The value of the identifier as a {@code String}.
     */
    public String getValue() {
        return value;
    }

    /**
     * Sets the value of this identifier.
     * This method allows changing the value of the identifier.
     *
     * @param value The new value of the identifier as a {@code String}.
     */
    public void setValue(String value) {
        this.value = value;
    }
}

Answer: throw new ACMEBadCsrException("CSR does not contain any domain names");

<think>
The method is parsing the CSR and extracting domain names. If the list is empty, the code should signal an error. The imported ACMEBadCsrException is intended for CSR-related errors, so the next line inside the if‑block should throw this exception with an appropriate message.
</think>
throw new ACMEBadCsrException("CSR does not contain any domain names");