Keyple Service C++ Library - 3.4.1
Component of the Keyple C++ middleware
ObservableLocalPluginAdapter.cpp
Go to the documentation of this file.
1/******************************************************************************
2 * Copyright (c) 2025 Calypso Networks Association https://calypsonet.org/ *
3 * *
4 * See the NOTICE file(s) distributed with this work for additional *
5 * information regarding copyright ownership. *
6 * *
7 * This program and the accompanying materials are made available under the *
8 * terms of the Eclipse Public License 2.0 which is available at *
9 * http://www.eclipse.org/legal/epl-2.0 *
10 * *
11 * SPDX-License-Identifier: EPL-2.0 *
12 ******************************************************************************/
13
14#include "keyple/core/service/ObservableLocalPluginAdapter.hpp"
15
16#include <memory>
17#include <string>
18#include <utility>
19#include <vector>
20
21#include "keyple/core/plugin/PluginIOException.hpp"
22#include "keyple/core/service/KeyplePluginException.hpp"
23#include "keyple/core/service/PluginEventAdapter.hpp"
24#include "keyple/core/util/cpp/Arrays.hpp"
25#include "keyple/core/util/cpp/exception/InterruptedException.hpp"
26
27namespace keyple {
28namespace core {
29namespace service {
30
31using keyple::core::plugin::PluginIOException;
32using keyple::core::util::cpp::Arrays;
33using keyple::core::util::cpp::exception::InterruptedException;
34
35/* OBSERVABLE LOCAL PLUGIN ADAPTER JOB
36 * ---------------------------------------------------------- */
37
38/* OBSERVABLE LOCAL PLUGIN ADAPTER
39 * -------------------------------------------------------------- */
40
41ObservableLocalPluginAdapter::ObservableLocalPluginAdapter(
42 std::shared_ptr<ObservablePluginSpi> observablePluginSpi)
43: AbstractObservableLocalPluginAdapter(observablePluginSpi)
44, mObservablePluginSpi(observablePluginSpi)
45{
46}
47
48ObservableLocalPluginAdapter::~ObservableLocalPluginAdapter()
49{
50 if (mThread) {
51 mThread->end();
52 while (mThread->mRunning || !mThread->mTerminated) {
53 }
54 }
55}
56
57bool
58ObservableLocalPluginAdapter::isMonitoring() const
59{
60 return mThread != nullptr && mThread->isAlive() && mThread->isMonitoring();
61}
62
63void
64ObservableLocalPluginAdapter::addObserver(
65 std::shared_ptr<PluginObserverSpi> observer)
66{
67 AbstractObservableLocalPluginAdapter::addObserver(observer);
68
69 if (countObservers() == 1) {
70 mLogger->info("[plugin=%] Starting reader monitoring\n", getName());
71 mThread = std::make_shared<EventThread>(getName(), this);
72 mThread->setName("PluginEventMonitoringThread");
73 mThread->setUncaughtExceptionHandler(
74 std::make_shared<UncaughtExceptionHandler>(this));
75 mThread->start();
76 while (!mThread->mStarted) {
77 }
78 }
79}
80
81void
82ObservableLocalPluginAdapter::removeObserver(
83 const std::shared_ptr<PluginObserverSpi> observer)
84{
85 Assert::getInstance().notNull(observer, "observer");
86
87 if (Arrays::contains(getObservationManager()->getObservers(), observer)) {
88 AbstractObservableLocalPluginAdapter::removeObserver(observer);
89
90 if (countObservers() == 0) {
91 if (mThread != nullptr) {
92 mThread->end();
93 mLogger->info(
94 "[plugin=%] Reader monitoring stopped\n", getName());
95 }
96 }
97 }
98}
99
100void
101ObservableLocalPluginAdapter::clearObservers()
102{
103 AbstractObservableLocalPluginAdapter::clearObservers();
104
105 if (mThread != nullptr) {
106 mThread->end();
107 mLogger->info("[plugin=%] Reader monitoring stopped\n", getName());
108 }
109}
110
111/* UNCAUGHT EXCEPTION HANDLER
112 * ------------------------------------------------------------------- */
113
114ObservableLocalPluginAdapter::UncaughtExceptionHandler::
115 UncaughtExceptionHandler(ObservableLocalPluginAdapter* parent)
116: mParent(parent)
117{
118}
119
120void
121ObservableLocalPluginAdapter::UncaughtExceptionHandler::uncaughtException(
122 std::shared_ptr<Thread> /*t*/, std::unique_ptr<Exception> e)
123{
124 mParent->getObservationManager()
125 ->getObservationExceptionHandler()
126 ->onPluginObservationError(mParent->mThread->mPluginName, std::move(e));
127}
128
129/* EVENT THREAD
130 * ---------------------------------------------------------------------------------
131 */
132
133ObservableLocalPluginAdapter::EventThread::EventThread(
134 const std::string& pluginName, ObservableLocalPluginAdapter* parent)
135: Thread(std::string("ObservableLocalPluginAdapter-") + pluginName)
136, mPluginName(pluginName)
137, mMonitoringCycleDuration(
138 parent->mObservablePluginSpi->getMonitoringCycleDuration())
139, mRunning(true)
140, mStarted(false)
141, mTerminated(false)
142, mParent(parent)
143{
144}
145
146void
147ObservableLocalPluginAdapter::EventThread::end()
148{
149 mRunning = false;
150 interrupt();
151}
152
153bool
154ObservableLocalPluginAdapter::EventThread::isMonitoring() const
155{
156 return mRunning;
157}
158
159void
160ObservableLocalPluginAdapter::EventThread::addReader(
161 const std::string& readerName)
162{
163 std::shared_ptr<ReaderSpi> readerSpi
164 = mParent->mObservablePluginSpi->searchReader(readerName);
165 std::shared_ptr<LocalReaderAdapter> reader
166 = mParent->buildLocalReaderAdapter(readerSpi);
167
168 reader->doRegister();
169 mParent->getReadersMap().insert({reader->getName(), reader});
170
171 mParent->mLogger->info(
172 "[plugin=%] New plugged reader added to readers list [reader=%]\n",
173 mPluginName,
174 readerName);
175}
176
177void
178ObservableLocalPluginAdapter::EventThread::removeReader(
179 const std::shared_ptr<CardReader> reader)
180{
181 std::dynamic_pointer_cast<LocalReaderAdapter>(reader)->doUnregister();
182 mParent->getReadersMap().erase(reader->getName());
183
184 mParent->mLogger->info(
185 "[plugin=%] Unplugged reader removed from readers list [reader=%]\n",
186 mPluginName,
187 reader->getName());
188}
189
190void
191ObservableLocalPluginAdapter::EventThread::notifyChanges(
192 const PluginEvent::Type type,
193 const std::vector<std::string>& changedReaderNames)
194{
195 /* Grouped notification */
196 mParent->mLogger->trace(
197 "Notify reader %(s): %\n",
198 type == PluginEvent::Type::READER_CONNECTED ? "connection"
199 : "disconnection",
200 changedReaderNames);
201
202 mParent->notifyObservers(
203 std::make_shared<PluginEventAdapter>(
204 mPluginName, changedReaderNames, type));
205}
206
207void
208ObservableLocalPluginAdapter::EventThread::processChanges(
209 const std::vector<std::string>& actualNativeReaderNames)
210{
211 std::vector<std::string> changedReaderNames;
212
213 /*
214 * Parse the current readers list, notify for disappeared readers, update
215 * readers list
216 */
217 const std::vector<std::shared_ptr<CardReader>> readers
218 = mParent->getReaders();
219 for (const auto& reader : readers) {
220 if (!std::count(
221 actualNativeReaderNames.begin(),
222 actualNativeReaderNames.end(),
223 reader->getName())) {
224 changedReaderNames.push_back(reader->getName());
225 }
226 }
227
228 /* Notify disconnections if any and update the reader list */
229 if (!changedReaderNames.empty()) {
230 /* List update */
231 for (const auto& reader : readers) {
232 if (!std::count(
233 actualNativeReaderNames.begin(),
234 actualNativeReaderNames.end(),
235 reader->getName())) {
236 removeReader(reader);
237 }
238 }
239
240 mParent->notifyObservers(
241 std::make_shared<PluginEventAdapter>(
242 mPluginName,
243 changedReaderNames,
244 PluginEvent::Type::READER_DISCONNECTED));
245
246 /* Clean the list for a possible connection notification */
247 changedReaderNames.clear();
248 }
249
250 /* Parse the new readers list, notify for readers appearance, update readers
251 * list */
252 for (const auto& readerName : actualNativeReaderNames) {
253 const std::vector<std::string>& readerNames = mParent->getReaderNames();
254 if (!std::count(readerNames.begin(), readerNames.end(), readerName)) {
255 addReader(readerName);
256
257 /* Add to the notification list */
258 changedReaderNames.push_back(readerName);
259 }
260 }
261
262 /* Notify connections if any */
263 if (!changedReaderNames.empty()) {
264 mParent->notifyObservers(
265 std::make_shared<PluginEventAdapter>(
266 mPluginName,
267 changedReaderNames,
268 PluginEvent::Type::READER_CONNECTED));
269 }
270}
271
272void
273ObservableLocalPluginAdapter::EventThread::execute()
274{
275 mStarted = true;
276
277 try {
278 /* True while a reader enumeration outage is in progress, see below. */
279 bool isInError = false;
280
281 while (mRunning) {
282 try {
283 /* Retrieves the current readers names list */
284 const std::vector<std::string> actualNativeReaderNames
285 = mParent->mObservablePluginSpi
286 ->searchAvailableReaderNames();
287
288 /* Checks if it has changed this algorithm favors cases where
289 * nothing change */
290 const std::vector<std::string> currentlyRegisteredReaderNames
291 = mParent->getReaderNames();
292 if (!Arrays::containsAll(
293 currentlyRegisteredReaderNames, actualNativeReaderNames)
294 || !Arrays::containsAll(
295 actualNativeReaderNames,
296 currentlyRegisteredReaderNames)) {
297 processChanges(actualNativeReaderNames);
298 }
299
300 isInError = false;
301
302 } catch (const PluginIOException& e) {
303 /*
304 * Deliberate divergence from the Java reference, where this
305 * catch sits outside the loop and the first such error ends
306 * monitoring for good. On Windows, unplugging the LAST reader
307 * stops the Smart Card service, so a terminal behaviour leaves
308 * the plugin deaf even after the readers come back. Report the
309 * outage once, then keep polling so reconnections are seen.
310 */
311 if (!isInError) {
312 isInError = true;
313 auto kpe(
314 std::unique_ptr<
315 KeyplePluginException>(new KeyplePluginException(
316 "An error occurred while monitoring the readers",
317 e)));
318 mParent->getObservationManager()
319 ->getObservationExceptionHandler()
320 ->onPluginObservationError(mPluginName, std::move(kpe));
321 }
322 }
323
324 /* Sleep for a while, whether or not the cycle succeeded */
325 Thread::sleep(mMonitoringCycleDuration);
326 }
327 } catch (const InterruptedException& e) {
328 (void)e;
329 mParent->mLogger->info(
330 "[plugin=%] Reader monitoring stopped, possibly because there is "
331 "no more registered observer",
332 getName());
333
334 /* Restore interrupted state... */
335 interrupt();
336 }
337
338 mTerminated = true;
339}
340
341} /* namespace service */
342} /* namespace core */
343} /* namespace keyple */