diff --git a/CHANGELOG.md b/CHANGELOG.md index 19fecf5..137ad45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- `TaskCanceledException`: added constructor `(String message, Throwable cause)` for consistency + with the other exceptions of the package. ### Changed - Migrated the CI pipeline from Jenkins to GitHub Actions. +- `ReaderSpi`: clarified the contracts of `openPhysicalChannel()`, `closePhysicalChannel()` and + `checkCardPresence()` regarding physical channel state management (see Javadoc for details). +- `ReaderSpi.getPowerOnData()`: documented lifecycle (meaningful only after a successful + `openPhysicalChannel()`) and allowed empty return value. +- `CardRemovalWaiterNonBlockingSpi`: the service now uses `checkCardPresence()` instead of + `transmitApdu()` for card removal polling. This change is conditioned by the plugin API version: + the new behaviour applies only when the plugin declares API version `2.4` or higher. +- Various Javadoc corrections and minor clarifications. +### Fixed +- `CardRemovalWaiterAsynchronousApi`: corrected `@link` pointing to a deprecated SPI. ## [2.3.2] - 2025-04-18 ### Changed diff --git a/gradle.properties b/gradle.properties index d7a3340..64b9946 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ group = org.eclipse.keyple title = Keyple Plugin Java API description = API dedicated to plugins development -version = 2.3.3-SNAPSHOT +version = 2.4.0-SNAPSHOT # Java Configuration javaSourceLevel = 1.8 diff --git a/src/main/java/org/eclipse/keyple/core/plugin/CardRemovalWaiterAsynchronousApi.java b/src/main/java/org/eclipse/keyple/core/plugin/CardRemovalWaiterAsynchronousApi.java index 299ea43..ff1f9bb 100644 --- a/src/main/java/org/eclipse/keyple/core/plugin/CardRemovalWaiterAsynchronousApi.java +++ b/src/main/java/org/eclipse/keyple/core/plugin/CardRemovalWaiterAsynchronousApi.java @@ -13,7 +13,7 @@ /** * API associated to a {@link - * org.eclipse.keyple.core.plugin.spi.reader.observable.state.removal.WaitForCardRemovalAutonomousSpi} + * org.eclipse.keyple.core.plugin.spi.reader.observable.state.removal.CardRemovalWaiterAsynchronousSpi} * * @since 2.2.0 */ diff --git a/src/main/java/org/eclipse/keyple/core/plugin/PluginApiProperties.java b/src/main/java/org/eclipse/keyple/core/plugin/PluginApiProperties.java index 27bd0b7..ca46c08 100644 --- a/src/main/java/org/eclipse/keyple/core/plugin/PluginApiProperties.java +++ b/src/main/java/org/eclipse/keyple/core/plugin/PluginApiProperties.java @@ -22,7 +22,7 @@ public final class PluginApiProperties { * * @since 2.0.0 */ - public static final String VERSION = "2.3"; + public static final String VERSION = "2.4"; /** Private constructor */ private PluginApiProperties() {} diff --git a/src/main/java/org/eclipse/keyple/core/plugin/TaskCanceledException.java b/src/main/java/org/eclipse/keyple/core/plugin/TaskCanceledException.java index c18c315..151d21a 100644 --- a/src/main/java/org/eclipse/keyple/core/plugin/TaskCanceledException.java +++ b/src/main/java/org/eclipse/keyple/core/plugin/TaskCanceledException.java @@ -26,4 +26,13 @@ public class TaskCanceledException extends Exception { public TaskCanceledException(String message) { super(message); } + + /** + * @param message the message to identify the exception context + * @param cause the cause + * @since 2.3.3 + */ + public TaskCanceledException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/src/main/java/org/eclipse/keyple/core/plugin/spi/AutonomousObservablePluginSpi.java b/src/main/java/org/eclipse/keyple/core/plugin/spi/AutonomousObservablePluginSpi.java index f47516b..6d4ba08 100644 --- a/src/main/java/org/eclipse/keyple/core/plugin/spi/AutonomousObservablePluginSpi.java +++ b/src/main/java/org/eclipse/keyple/core/plugin/spi/AutonomousObservablePluginSpi.java @@ -26,6 +26,10 @@ public interface AutonomousObservablePluginSpi extends PluginSpi { /** * Connects the associated Keyple Core {@link AutonomousObservablePluginApi} API. * + *
This method is no longer called by the framework since version 2.2.0. Implementations + * migrating to {@link #setCallback(AutonomousObservablePluginApi)} may provide an empty body for + * this method. + * * @param autonomousObservablePluginApi The API to connect. * @since 2.0.0 * @deprecated Use {@link #setCallback(AutonomousObservablePluginApi)} instead. diff --git a/src/main/java/org/eclipse/keyple/core/plugin/spi/reader/ReaderSpi.java b/src/main/java/org/eclipse/keyple/core/plugin/spi/reader/ReaderSpi.java index 321fb82..290a80a 100644 --- a/src/main/java/org/eclipse/keyple/core/plugin/spi/reader/ReaderSpi.java +++ b/src/main/java/org/eclipse/keyple/core/plugin/spi/reader/ReaderSpi.java @@ -15,8 +15,10 @@ import org.eclipse.keyple.core.plugin.ReaderIOException; /** - * Reader able to communicate with smart cards whose purpose is to remain present in the reader (for - * example a SAM reader). + * Reader able to communicate with smart cards. + * + *
This is the base interface for all reader types, including readers with permanently present + * cards (e.g. SAM readers) and observable readers detecting card insertion and removal. * *
The target devices must comply with the following Calypsonet Terminal requirements: * @@ -40,27 +42,36 @@ public interface ReaderSpi { String getName(); /** - * Validates the opening of the physical channel. Performs the actual opening if this has not been - * done by the {@link #checkCardPresence()} method. In all cases, memorizes the new state for the - * operation of the {@link #isPhysicalChannelOpen()} method. After executing this method, the - * reader is able to send APDUs to the card. + * Ensures that the physical channel is open and that the card is ready to receive APDU commands. + * + *
On successful return: + * + *
If {@link #checkCardPresence()} has already opened the physical channel (e.g. for + * contactless readers performing anti-collision during presence detection), this method is a + * no-op. * * @throws ReaderIOException If the communication with the reader has failed. - * @throws CardIOException If the communication with the card has failed. + * @throws CardIOException If no card is present or if the communication with the card has failed. * @since 2.0.0 */ void openPhysicalChannel() throws ReaderIOException, CardIOException; /** - * Tells the reader that card processing is complete and that the next step is to remove the card - * from the reader. + * Closes the physical channel. * - *
If the reader has the ability to sense the presence of the card without communicating with - * it, then this method must proceed to the actual closing of the physical channel (e.g. power - * down in the case of a contact reader). Otherwise, this method is limited to changing the - * logical opening state of the physical channel and letting the removal procedure do the closing. + *
Depending on the reader's capabilities, this method will either use a card presence - * indicator without necessarily communicating with the card (for example, in the case of a - * contact reader equipped with a physical insertion detector using a switch), or will communicate - * with the card (in the case of contactless hunting). In the latter case, we can consider that - * the physical channel has been opened (and therefore no longer needs to be opened in the {@link - * #openPhysicalChannel()} method). + *
The behavior of this method depends on the state of the physical channel: + * + *
The power-on data is defined as the data retrieved by the reader when the card is inserted. + * This method is only meaningful after {@link #openPhysicalChannel()} has returned successfully. * *
In the case of a contact reader, this is the Answer To Reset data (ATR) defined by ISO7816. * @@ -101,9 +119,11 @@ public interface ReaderSpi { * the ISO14443 protocol (ATQA, ATQB, ATS, SAK, etc). * *
These data being variable from one reader to another, they are defined here in string format - * which can be either a hexadecimal string or any other relevant information. + * which can be either a hexadecimal string or any other relevant information. An empty string may + * be returned if no power-on data is available (e.g. for a reader with a permanently powered + * card). * - * @return A not empty String. + * @return A non-null String, possibly empty. * @since 2.0.0 */ String getPowerOnData(); @@ -111,9 +131,11 @@ public interface ReaderSpi { /** * Transmits an APDU and returns its response. * - *
Caution: the implementation must handle the case where the card response is 61xy and - * execute the appropriate get response command (Calypsonet Terminal requirement - * "RL-SW-61XX.1"). + *
Caution: the implementation must handle the ISO 7816-3 T=0 protocol specificity at the + * transport level: status word {@code 61xx} (response bytes available) requires automatically + * issuing a {@code GET RESPONSE} command (Calypsonet Terminal requirement "RL-SW-61XX.1"). This + * is handled at the SPI level because its behavior depends on the underlying reader + * implementation (T=0, T=1, PC/SC). * * @param apduIn The data to be sent to the card. * @return A buffer of at least 2 bytes. diff --git a/src/main/java/org/eclipse/keyple/core/plugin/spi/reader/observable/ObservableReaderSpi.java b/src/main/java/org/eclipse/keyple/core/plugin/spi/reader/observable/ObservableReaderSpi.java index 2f9d5ce..3620560 100644 --- a/src/main/java/org/eclipse/keyple/core/plugin/spi/reader/observable/ObservableReaderSpi.java +++ b/src/main/java/org/eclipse/keyple/core/plugin/spi/reader/observable/ObservableReaderSpi.java @@ -13,13 +13,14 @@ import org.eclipse.keyple.core.plugin.spi.reader.ReaderSpi; import org.eclipse.keyple.core.plugin.spi.reader.observable.state.insertion.*; +import org.eclipse.keyple.core.plugin.spi.reader.observable.state.processing.*; import org.eclipse.keyple.core.plugin.spi.reader.observable.state.removal.*; /** * Reader able to detect the insertion and removal of cards. * *
In addition, an observable reader must also define its observation capabilities for the card - * insertion and removal steps. + * insertion, removal, and optionally processing steps. * *
For the card insertion state, it must implement one of the following interfaces: * @@ -37,6 +38,18 @@ *
For the card processing state (monitoring card presence between APDU commands), it may + * optionally implement: + * + *
Readers implementing {@link CardRemovalWaiterAsynchronousSpi} (e.g. Android NFC readers) do + * not need to implement {@link CardPresenceMonitorBlockingSpi}: the asynchronous removal callback + * covers all phases, including processing. + * * @since 2.0.0 */ public interface ObservableReaderSpi extends ReaderSpi { diff --git a/src/main/java/org/eclipse/keyple/core/plugin/spi/reader/observable/state/processing/CardPresenceMonitorBlockingSpi.java b/src/main/java/org/eclipse/keyple/core/plugin/spi/reader/observable/state/processing/CardPresenceMonitorBlockingSpi.java index a6151d0..fdff8b3 100644 --- a/src/main/java/org/eclipse/keyple/core/plugin/spi/reader/observable/state/processing/CardPresenceMonitorBlockingSpi.java +++ b/src/main/java/org/eclipse/keyple/core/plugin/spi/reader/observable/state/processing/CardPresenceMonitorBlockingSpi.java @@ -24,12 +24,12 @@ public interface CardPresenceMonitorBlockingSpi { /** - * Monitors the card presence indefinitely (the method is blocking as long as the card is - * present). + * Monitors the card presence indefinitely (the method is blocking as long as the card is present) + * and returns normally when the card is removed. * *
This monitoring can be cancelled for an internal (for example timeout) or external reason - * (for example invocation of {@link #stopCardPresenceMonitoringDuringProcessing()}), in this case - * an exception is raised. + * (for example invocation of {@link #stopCardPresenceMonitoringDuringProcessing()}), in which + * case an exception is raised. * * @throws ReaderIOException If the communication with the reader has failed. * @throws TaskCanceledException If the task has been canceled and is no longer active. diff --git a/src/main/java/org/eclipse/keyple/core/plugin/spi/reader/observable/state/removal/CardRemovalWaiterBlockingSpi.java b/src/main/java/org/eclipse/keyple/core/plugin/spi/reader/observable/state/removal/CardRemovalWaiterBlockingSpi.java index e1b3803..1982520 100644 --- a/src/main/java/org/eclipse/keyple/core/plugin/spi/reader/observable/state/removal/CardRemovalWaiterBlockingSpi.java +++ b/src/main/java/org/eclipse/keyple/core/plugin/spi/reader/observable/state/removal/CardRemovalWaiterBlockingSpi.java @@ -32,7 +32,7 @@ public interface CardRemovalWaiterBlockingSpi { *
This wait can be cancelled for an internal (for example timeout) or external reason (for * example invocation of {@link #stopWaitForCardRemoval()}), in this case an exception is raised. * - * @throws ReaderIOException If the communication with the reader + * @throws ReaderIOException If the communication with the reader has failed. * @throws TaskCanceledException If the task has been canceled and is no longer active * @since 2.2.0 */ diff --git a/src/main/java/org/eclipse/keyple/core/plugin/spi/reader/observable/state/removal/CardRemovalWaiterNonBlockingSpi.java b/src/main/java/org/eclipse/keyple/core/plugin/spi/reader/observable/state/removal/CardRemovalWaiterNonBlockingSpi.java index baaeddd..d568511 100644 --- a/src/main/java/org/eclipse/keyple/core/plugin/spi/reader/observable/state/removal/CardRemovalWaiterNonBlockingSpi.java +++ b/src/main/java/org/eclipse/keyple/core/plugin/spi/reader/observable/state/removal/CardRemovalWaiterNonBlockingSpi.java @@ -12,20 +12,20 @@ package org.eclipse.keyple.core.plugin.spi.reader.observable.state.removal; /** - * This SPI is specifically designed for plugins that don't handle card removal autonomously but - * requires the sending of an APDU to detect the card removal. + * This SPI is specifically designed for plugins that don't handle card removal autonomously. * *
When a plugin implements this SPI, the {@link - * org.eclipse.keyple.core.plugin.spi.reader.ReaderSpi#transmitApdu(byte[])} method will be called + * org.eclipse.keyple.core.plugin.spi.reader.ReaderSpi#checkCardPresence()} method will be called * periodically by the service when a card removal is expected. The card is considered removed when - * the transmission fails. + * {@code checkCardPresence()} returns {@code false} (which also triggers the internal closure of + * the physical channel). * - *
The value returned by the {@link #getCardRemovalMonitoringSleepDuration()} will be used as an + *
The value returned by {@link #getCardRemovalMonitoringSleepDuration()} will be used as an * argument to {@link Thread#sleep(long)} between two calls to {@link - * org.eclipse.keyple.core.plugin.spi.reader.ReaderSpi#transmitApdu}. + * org.eclipse.keyple.core.plugin.spi.reader.ReaderSpi#checkCardPresence()}. * *
A typical example of readers conforming to this mode of operation are terminals embedding a
- * slave RF communication module without card presence feature.
+ * slave RF communication module without autonomous card presence detection.
*
* @since 2.2.0
*/
@@ -33,7 +33,7 @@ public interface CardRemovalWaiterNonBlockingSpi {
/**
* Provides the value of the sleep duration (in milliseconds) inserted between two calls to {@link
- * org.eclipse.keyple.core.plugin.spi.reader.ReaderSpi#transmitApdu}.
+ * org.eclipse.keyple.core.plugin.spi.reader.ReaderSpi#checkCardPresence()}.
*
* @return A positive value (0 is allowed).
* @since 2.2.0
diff --git a/src/main/uml/api_class_diagram.puml b/src/main/uml/api_class_diagram.puml
index 7bc2abf..0304cea 100644
--- a/src/main/uml/api_class_diagram.puml
+++ b/src/main/uml/api_class_diagram.puml
@@ -1,6 +1,6 @@
@startuml
title
- Keyple - keyple-plugin-java-api - 2.3.+ (2024-03-26)
+ Keyple - keyple-plugin-java-api - 2.4.+ (2026-03-31)
end title
' == THEME ==
@@ -88,6 +88,8 @@ package "org.eclipse.keyple.core.plugin" as api {
}
+class "<DontWaitForCardRemovalDuringProcessingSpi" as DontWaitForCardRemovalDuringProcessingSpi <