Keyple Service C++ Library - 3.3.5
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("Start monitoring the plugin [%]\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("Plugin monitoring stopped\n");
94 }
95 }
96 }
97}
98
99void
100ObservableLocalPluginAdapter::clearObservers()
101{
102 AbstractObservableLocalPluginAdapter::clearObservers();
103
104 if (mThread != nullptr) {
105 mThread->end();
106 mLogger->info("Plugin monitoring stopped\n");
107 }
108}
109
110/* UNCAUGHT EXCEPTION HANDLER
111 * ------------------------------------------------------------------- */
112
113ObservableLocalPluginAdapter::UncaughtExceptionHandler::
114 UncaughtExceptionHandler(ObservableLocalPluginAdapter* parent)
115: mParent(parent)
116{
117}
118
119void
120ObservableLocalPluginAdapter::UncaughtExceptionHandler::uncaughtException(
121 std::shared_ptr<Thread> /*t*/, std::unique_ptr<Exception> e)
122{
123 mParent->getObservationManager()
124 ->getObservationExceptionHandler()
125 ->onPluginObservationError(mParent->mThread->mPluginName, std::move(e));
126}
127
128/* EVENT THREAD
129 * ---------------------------------------------------------------------------------
130 */
131
132ObservableLocalPluginAdapter::EventThread::EventThread(
133 const std::string& pluginName, ObservableLocalPluginAdapter* parent)
134: Thread(std::string("ObservableLocalPluginAdapter-") + pluginName)
135, mPluginName(pluginName)
136, mMonitoringCycleDuration(
137 parent->mObservablePluginSpi->getMonitoringCycleDuration())
138, mRunning(true)
139, mStarted(false)
140, mTerminated(false)
141, mParent(parent)
142{
143}
144
145void
146ObservableLocalPluginAdapter::EventThread::end()
147{
148 mRunning = false;
149 interrupt();
150}
151
152bool
153ObservableLocalPluginAdapter::EventThread::isMonitoring() const
154{
155 return mRunning;
156}
157
158void
159ObservableLocalPluginAdapter::EventThread::addReader(
160 const std::string& readerName)
161{
162 std::shared_ptr<ReaderSpi> readerSpi
163 = mParent->mObservablePluginSpi->searchReader(readerName);
164 std::shared_ptr<LocalReaderAdapter> reader
165 = mParent->buildLocalReaderAdapter(readerSpi);
166
167 reader->doRegister();
168 mParent->getReadersMap().insert({reader->getName(), reader});
169
170 mParent->mLogger->info(
171 "Plugin [%] adds plugged reader [%] to readers list\n",
172 mPluginName,
173 readerName);
174}
175
176void
177ObservableLocalPluginAdapter::EventThread::removeReader(
178 const std::shared_ptr<CardReader> reader)
179{
180 std::dynamic_pointer_cast<LocalReaderAdapter>(reader)->doUnregister();
181 mParent->getReadersMap().erase(reader->getName());
182
183 mParent->mLogger->info(
184 "Plugin [%] removes unplugged reader [%] from readers list\n",
185 mPluginName,
186 reader->getName());
187}
188
189void
190ObservableLocalPluginAdapter::EventThread::notifyChanges(
191 const PluginEvent::Type type,
192 const std::vector<std::string>& changedReaderNames)
193{
194 /* Grouped notification */
195 mParent->mLogger->trace(
196 "Notify reader %(s): %\n",
197 type == PluginEvent::Type::READER_CONNECTED ? "connection"
198 : "disconnection",
199 changedReaderNames);
200
201 mParent->notifyObservers(
202 std::make_shared<PluginEventAdapter>(
203 mPluginName, changedReaderNames, type));
204}
205
206void
207ObservableLocalPluginAdapter::EventThread::processChanges(
208 const std::vector<std::string>& actualNativeReaderNames)
209{
210 std::vector<std::string> changedReaderNames;
211
212 /* Parse the current readers list, notify for disappeared readers, update
213 * readers list */
214 const std::vector<std::shared_ptr<CardReader>> readers
215 = mParent->getReaders();
216 for (const auto& reader : readers) {
217 if (!std::count(
218 actualNativeReaderNames.begin(),
219 actualNativeReaderNames.end(),
220 reader->getName())) {
221 changedReaderNames.push_back(reader->getName());
222 }
223 }
224
225 /* Notify disconnections if any and update the reader list */
226 if (!changedReaderNames.empty()) {
227 /* List update */
228 for (const auto& reader : readers) {
229 if (!std::count(
230 actualNativeReaderNames.begin(),
231 actualNativeReaderNames.end(),
232 reader->getName())) {
233 removeReader(reader);
234 }
235 }
236
237 notifyChanges(
238 PluginEvent::Type::READER_DISCONNECTED, changedReaderNames);
239
240 /* Clean the list for a possible connection notification */
241 changedReaderNames.clear();
242 }
243
244 /* Parse the new readers list, notify for readers appearance, update readers
245 * list */
246 for (const auto& readerName : actualNativeReaderNames) {
247 const std::vector<std::string>& readerNames = mParent->getReaderNames();
248 if (!std::count(readerNames.begin(), readerNames.end(), readerName)) {
249 addReader(readerName);
250
251 /* Add to the notification list */
252 changedReaderNames.push_back(readerName);
253 }
254 }
255
256 /* Notify connections if any */
257 if (!changedReaderNames.empty()) {
258 notifyChanges(PluginEvent::Type::READER_CONNECTED, changedReaderNames);
259 }
260}
261
262void
263ObservableLocalPluginAdapter::EventThread::execute()
264{
265 mStarted = true;
266
267 /* True while a reader enumeration outage is in progress, see below. */
268 bool isInError = false;
269
270 try {
271 while (mRunning) {
272 try {
273 /* Retrieves the current readers names list */
274 const std::vector<std::string> actualNativeReaderNames
275 = mParent->mObservablePluginSpi
276 ->searchAvailableReaderNames();
277
278 /* Checks if it has changed this algorithm favors cases where
279 * nothing change */
280 const std::vector<std::string> currentlyRegisteredReaderNames
281 = mParent->getReaderNames();
282 if (!Arrays::containsAll(
283 currentlyRegisteredReaderNames, actualNativeReaderNames)
284 || !Arrays::containsAll(
285 actualNativeReaderNames,
286 currentlyRegisteredReaderNames)) {
287 processChanges(actualNativeReaderNames);
288 }
289
290 isInError = false;
291
292 } catch (const PluginIOException& e) {
293 /*
294 * Deliberate divergence from the Java reference, where this
295 * catch sits outside the loop and the first such error ends
296 * monitoring for good. On Windows, unplugging the LAST reader
297 * stops the Smart Card service, so a terminal behaviour leaves
298 * the plugin deaf even after the readers come back. Report the
299 * outage once, then keep polling so reconnections are seen.
300 */
301 if (!isInError) {
302 isInError = true;
303 auto kpe(std::unique_ptr<KeyplePluginException>(
304 new KeyplePluginException(
305 "An error occurred while monitoring the readers",
306 e)));
307 mParent->getObservationManager()
308 ->getObservationExceptionHandler()
309 ->onPluginObservationError(mPluginName, std::move(kpe));
310 }
311 }
312
313 /* Sleep for a while, whether or not the cycle succeeded */
314 Thread::sleep(mMonitoringCycleDuration);
315 }
316 } catch (const InterruptedException& e) {
317 (void)e;
318 mParent->mLogger->info(
319 "Plugin monitoring stopped, possibly because there is no more "
320 "registered observer");
321
322 /* Restore interrupted state... */
323 interrupt();
324 }
325
326 mTerminated = true;
327}
328
329} /* namespace service */
330} /* namespace core */
331} /* namespace keyple */