Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ public interface AutonomousObservablePluginSpi extends PluginSpi {
/**
* Connects the associated Keyple Core {@link AutonomousObservablePluginApi} API.
*
* <p>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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>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.
*
* <p>The target devices must comply with the following Calypsonet Terminal requirements:
*
Expand All @@ -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.
*
* <p>On successful return:
*
* <ul>
* <li>the power-on data are available via {@link #getPowerOnData()};
* <li>the card is ready to receive APDU commands via {@link #transmitApdu(byte[])}.
* </ul>
*
* <p>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.
*
* <p>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.
* <ul>
* <li><b>Card present:</b> physically closes the channel (e.g. cuts the RF field, powers down
* the card, or performs a PC/SC reset).
* <li><b>Card absent:</b> no-op.
* </ul>
*
* @throws ReaderIOException If the communication with the reader has failed.
* @throws ReaderIOException If the card is present and the close operation fails (reader
* problem).
* @since 2.0.0
*/
void closePhysicalChannel() throws ReaderIOException;
Expand All @@ -69,20 +80,26 @@ public interface ReaderSpi {
* Tells if the physical channel is open or not.
*
* @return True is the physical channel is open, false if not.
* @since 2.0.0
*/
boolean isPhysicalChannelOpen();

/**
* Verifies the presence of a card.
*
* <p>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).
* <p>The behavior of this method depends on the state of the physical channel:
*
* <ul>
* <li><b>Physical channel closed:</b> performs a best-effort one-shot detection, starting the
* RF field or powering up the card if necessary. If this detection also opens the physical
* channel (e.g. for contactless readers performing anti-collision), a subsequent call to
* {@link #openPhysicalChannel()} is a no-op.
* <li><b>Physical channel open:</b> verifies that the card is still present using the
* underlying SDK capabilities (e.g. ping APDU). If the card is no longer present, {@link
* #closePhysicalChannel()} is called internally before returning {@code false}.
* </ul>
*
* @return True if a card is present
* @return {@code true} if a card is present, {@code false} otherwise.
* @throws ReaderIOException If the communication with the reader has failed.
* @since 2.0.0
*/
Expand All @@ -92,6 +109,7 @@ public interface ReaderSpi {
* Gets the power-on data.
*
* <p>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.
*
* <p>In the case of a contact reader, this is the Answer To Reset data (ATR) defined by ISO7816.
*
Expand All @@ -101,19 +119,23 @@ public interface ReaderSpi {
* the ISO14443 protocol (ATQA, ATQB, ATS, SAK, etc).
*
* <p>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();

/**
* Transmits an APDU and returns its response.
*
* <p><b>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").</b>
* <p><b>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).</b>
*
* @param apduIn The data to be sent to the card.
* @return A buffer of at least 2 bytes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>In addition, an observable reader must also define its observation capabilities for the card
* insertion and removal steps.
* insertion, removal, and optionally processing steps.
*
* <p>For the card insertion state, it must implement one of the following interfaces:
*
Expand All @@ -37,6 +38,18 @@
* <li>{@link CardRemovalWaiterNonBlockingSpi}
* </ul>
*
* <p>For the card processing state (monitoring card presence between APDU commands), it may
* optionally implement:
*
* <ul>
* <li>{@link CardPresenceMonitorBlockingSpi} for readers capable of blocking card presence
* monitoring (e.g. PC/SC readers).
* </ul>
*
* <p>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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public interface CardRemovalWaiterBlockingSpi {
* <p>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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,28 @@
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.
*
* <p>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).
*
* <p>The value returned by the {@link #getCardRemovalMonitoringSleepDuration()} will be used as an
* <p>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()}.
*
* <p>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
*/
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
Expand Down
10 changes: 6 additions & 4 deletions src/main/uml/api_class_diagram.puml
Original file line number Diff line number Diff line change
@@ -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 ==
Expand Down Expand Up @@ -88,6 +88,8 @@ package "org.eclipse.keyple.core.plugin" as api {
}
+class "<<Exception>>\n**final** TaskCanceledException" as TaskCanceledException <<green>> {
+TaskCanceledException (String message)
+<color:blue>TaskCanceledException (String message,
<color:blue> \tThrowable cause)
}
package spi {
together {
Expand Down Expand Up @@ -169,9 +171,9 @@ package "org.eclipse.keyple.core.plugin" as api {
}
+interface "<s>DontWaitForCardRemovalDuringProcessingSpi" as DontWaitForCardRemovalDuringProcessingSpi <<red>> {
}
+interface "<color:blue>CardPresenceMonitorBlockingSpi" as CardPresenceMonitorBlockingSpi <<red>> {
+<color:blue>void monitorCardPresenceDuringProcessing ()
+<color:blue>void stopCardPresenceMonitoringDuringProcessing ()
+interface "CardPresenceMonitorBlockingSpi" as CardPresenceMonitorBlockingSpi <<red>> {
+void monitorCardPresenceDuringProcessing ()
+void stopCardPresenceMonitoringDuringProcessing ()
}
}
package insertion {
Expand Down
2 changes: 1 addition & 1 deletion src/main/uml/api_class_diagram.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading