FOSSredder 0.5.0
Loading...
Searching...
No Matches
WorkspaceStateManager.cpp
Go to the documentation of this file.
1
5
6#include "core/pch.h"
7
10
20
30
31#include <algorithm>
32#include <stdexcept>
33#include <type_traits>
34#include <unordered_map>
35#include <unordered_set>
36#include <utility>
37
38namespace core::application {
39
40namespace {
41
43template <typename RepoPtr, typename LoadFn>
44auto loadCollection(const RepoPtr& repo, LoadFn&& load) {
45 using Collection = std::decay_t<decltype(load(*repo))>;
46 if (!repo) return Collection{};
47 return load(*repo);
48} // namespace core::application
49
51template <typename RepoPtr, typename Collection, typename UpsertFn>
52void upsertCollection(const RepoPtr& repo, const Collection& items, UpsertFn&& upsert) {
53 if (!repo) return;
54 for (const auto& item : items) {
55 if (!item) continue;
56 upsert(*repo, item);
57 }
58}
59
61template <typename RepoPtr, typename Collection, typename ClearFn, typename UpsertFn>
62void replaceCollection(const RepoPtr& repo, const Collection& items, ClearFn&& clear, UpsertFn&& upsert) {
63 if (!repo) return;
64
65 clear(*repo);
66 for (const auto& item : items) {
67 if (!item) continue;
68 upsert(*repo, item);
69 }
70}
71
72void dedupStrings(std::vector<std::string>& values) {
73 std::unordered_set<std::string> seen;
74 std::vector<std::string> out;
75 out.reserve(values.size());
76 for (const auto& value : values) {
77 if (value.empty()) continue;
78 if (!seen.insert(value).second) continue;
79 out.push_back(value);
80 }
81 std::sort(out.begin(), out.end());
82 values = std::move(out);
83}
84
85void dedupAliases(std::vector<core::domain::Alias>& values) {
86 std::unordered_set<std::string> seen;
87 std::vector<core::domain::Alias> out;
88 out.reserve(values.size());
89 for (const auto& value : values) {
90 if (value.value().empty()) continue;
91 if (!seen.insert(value.value()).second) continue;
92 out.push_back(value);
93 }
94 values = std::move(out);
95}
96
97void normalizeAliases(std::vector<core::domain::Alias>& aliases) {
98 for (auto& alias : aliases) {
99 if (alias.value().empty()) alias.setValue(alias.source());
100 if (alias.source().empty()) alias.setSource(alias.value());
101 }
102}
103
104void projectActorPropertyLinksFromContracts(core::application::workspace::WorkspaceSessionState& document) {
105 const auto& contracts = document.catalog.contracts();
106
107 for (auto& actor : document.catalog.actors()) {
108 if (!actor) continue;
109 std::vector<std::string> contractIds;
110 for (const auto& contract : contracts) {
111 if (contract && contract->containsActorId(actor->id())) {
112 contractIds.push_back(contract->id());
113 }
114 }
115 dedupStrings(contractIds);
116 actor->setContractIds(std::move(contractIds));
117 }
118
119 for (auto& property : document.catalog.properties()) {
120 if (!property) continue;
121 std::vector<std::string> contractIds;
122 for (const auto& contract : contracts) {
123 if (contract && contract->containsPropertyId(property->id())) {
124 contractIds.push_back(contract->id());
125 }
126 }
127 dedupStrings(contractIds);
128 property->setContractIds(std::move(contractIds));
129 }
130}
131
132template <typename T>
133std::shared_ptr<T> cloneEntity(const std::shared_ptr<T>& entity) {
134 return entity;
135}
136
137template <typename Dst, typename Src>
138void cloneCollection(Dst& dst, const Src& src) {
139 dst.reserve(src.size());
140 for (const auto& item : src) {
141 dst.push_back(cloneEntity(item));
142 }
143}
144
145void rehydrate(core::application::workspace::WorkspaceSessionState& document) {
146 auto& state = document.catalog;
147 for (auto& actor : state.actors()) {
148 if (!actor) continue;
149 auto aliases = actor->aliases();
150 dedupAliases(aliases);
151 actor->setAliases(std::move(aliases));
152 }
153 for (auto& property : state.properties()) {
154 if (!property) continue;
155 auto aliases = property->aliases();
156 dedupAliases(aliases);
157 property->setAliases(std::move(aliases));
158 }
159 for (auto& contract : state.contracts()) {
160 if (!contract || contract->id().empty()) continue;
161 auto aliases = contract->aliases();
162 dedupAliases(aliases);
163 contract->setAliases(std::move(aliases));
164 auto actorIds = contract->actorIds();
165 dedupStrings(actorIds);
166 contract->setActorIds(std::move(actorIds));
167 auto propertyIds = contract->propertyIds();
168 dedupStrings(propertyIds);
169 contract->setPropertyIds(std::move(propertyIds));
170 }
171 for (auto& statement : state.statements()) {
172 if (!statement) continue;
173 auto transactionIds = statement->transactionIds();
174 dedupStrings(transactionIds);
175 statement->setTransactionIds(std::move(transactionIds));
176 }
177 for (auto& annual : state.annuals()) {
178 if (!annual) continue;
179 auto analysisIds = annual->analysisIds();
180 dedupStrings(analysisIds);
181 annual->setAnalysisIds(std::move(analysisIds));
182 }
183 projectActorPropertyLinksFromContracts(document);
184}
185
186void validate(const core::application::workspace::WorkspaceSessionState& document, bool strict) {
187 const auto& state = document.catalog;
188 const auto fail = [strict](const std::string& msg) {
189 if (strict) throw std::runtime_error(msg);
190 };
191
192 for (const auto& contract : state.contracts()) {
193 if (!contract) continue;
194 if (!core::domain::EntityName::isValid(contract->name())) fail("Contract.name is invalid");
195 if (!core::domain::ContractType::isValid(contract->type()) && !contract->type().empty()) fail("Contract.type is invalid");
196 if (!contract->hasRelations()) fail("Contract has no relations");
197 }
198
199 std::unordered_map<std::string, const core::domain::Statement*> statementById;
200 for (const auto& statement : state.statements()) {
201 if (statement && !statement->id().empty()) {
202 statementById.emplace(statement->id(), statement.get());
203 }
204 }
205
206 std::unordered_set<std::string> transactionIds;
207 for (const auto& transaction : state.transactions()) {
208 if (transaction && !core::domain::EntityName::isValid(transaction->name())) fail("Transaction.name is invalid");
209 if (transaction && !transaction->id().empty()) transactionIds.insert(transaction->id());
210 }
211
212 std::unordered_set<std::string> actorIds;
213 std::unordered_set<std::string> contractIds;
214 std::unordered_set<std::string> analysisIds;
215 for (const auto& actor : state.actors()) {
216 if (actor && !actor->id().empty()) actorIds.insert(actor->id());
217 }
218 for (const auto& contract : state.contracts()) {
219 if (contract && !contract->id().empty()) contractIds.insert(contract->id());
220 }
221 for (const auto& analysis : state.analyses()) {
222 if (analysis && !analysis->id().empty()) {
223 analysisIds.insert(analysis->id());
224 if (!core::domain::EntityName::isValid(analysis->name())) fail("Analysis.name is invalid");
225 if (!analysis->hasType() && analysis->hasExportFormat()) fail("Analysis.exportFormat without type");
226 if (analysis->hasType() && !analysis->hasExportFormat()) fail("Analysis.type without exportFormat");
227 if (analysis->isConfigured() && !analysis->isResultReady()) fail("Analysis configuration is incomplete");
228 }
229 }
230
231 for (const auto& transaction : state.transactions()) {
232 if (!transaction) continue;
233 if (transaction->hasActor() && !actorIds.count(transaction->actorId())) fail("Transaction.actorId unresolved");
234 if (transaction->hasContract() && !contractIds.count(transaction->contractId())) fail("Transaction.contractId unresolved");
235 if (transaction->hasStatement()) {
236 const auto it = statementById.find(transaction->statementId());
237 if (it == statementById.end()) {
238 fail("Transaction.statementId unresolved");
239 } else if (!it->second->containsTransaction(transaction->id())) {
240 fail("Transaction missing from owning statement");
241 }
242 }
243 }
244
245 for (const auto& annual : state.annuals()) {
246 if (!annual) continue;
248 fail("Annual.name is invalid");
249 }
250 if (!annual->hasYear()) {
251 fail("Annual.year is invalid");
252 }
253 for (const auto& analysisId : annual->analysisIds()) {
254 if (!analysisIds.count(analysisId)) {
255 fail("Annual references missing analysis");
256 }
257 }
258 }
259
260 for (const auto& actor : state.actors()) {
261 if (actor && !core::domain::EntityName::isValid(actor->name())) {
262 fail("Actor.name is invalid");
263 }
264 if (actor) {
265 for (const auto& contractId : actor->contractIds()) {
266 if (!contractIds.count(contractId)) {
267 fail("Actor references missing contract");
268 }
269 }
270 }
271 }
272 for (const auto& property : state.properties()) {
273 if (property && !core::domain::EntityName::isValid(property->name())) {
274 fail("Property.name is invalid");
275 }
276 if (property) {
277 for (const auto& contractId : property->contractIds()) {
278 if (!contractIds.count(contractId)) {
279 fail("Property references missing contract");
280 }
281 }
282 }
283 }
284 for (const auto& statement : state.statements()) {
285 if (statement && !core::domain::EntityName::isValid(statement->name())) {
286 fail("Statement.name is invalid");
287 }
288 }
289 for (const auto& statement : state.statements()) {
290 if (!statement) continue;
291 for (const auto& transactionId : statement->transactionIds()) {
292 if (!transactionIds.count(transactionId)) {
293 fail("Statement references missing transaction");
294 }
295 }
296 }
297}
298
299core::application::workspace::WorkspaceSessionState prepareForSave(const core::application::workspace::WorkspaceSessionState& document) {
300 core::application::workspace::WorkspaceSessionState out;
308 cloneCollection(actors, document.catalog.actors());
309 cloneCollection(properties, document.catalog.properties());
310 cloneCollection(contracts, document.catalog.contracts());
311 cloneCollection(statements, document.catalog.statements());
312 cloneCollection(transactions, document.catalog.transactions());
313 cloneCollection(analyses, document.catalog.analyses());
314 cloneCollection(annuals, document.catalog.annuals());
315 out.catalog.setActors(std::move(actors));
316 out.catalog.setProperties(std::move(properties));
317 out.catalog.setContracts(std::move(contracts));
318 out.catalog.setStatements(std::move(statements));
319 out.catalog.setTransactions(std::move(transactions));
320 out.catalog.setAnalyses(std::move(analyses));
321 out.catalog.setAnnuals(std::move(annuals));
322 out.workflow = document.workflow;
323
324 for (const auto& actor : out.catalog.actors()) {
325 if (actor) {
326 auto aliases = actor->aliases();
327 normalizeAliases(aliases);
328 dedupAliases(aliases);
329 actor->setAliases(std::move(aliases));
330 }
331 }
332 for (const auto& property : out.catalog.properties()) {
333 if (property) {
334 auto aliases = property->aliases();
335 normalizeAliases(aliases);
336 dedupAliases(aliases);
337 property->setAliases(std::move(aliases));
338 }
339 }
340 for (const auto& contract : out.catalog.contracts()) {
341 if (!contract) continue;
342 auto aliases = contract->aliases();
343 normalizeAliases(aliases);
344 dedupAliases(aliases);
345 contract->setAliases(std::move(aliases));
346 auto actorIds = contract->actorIds();
347 dedupStrings(actorIds);
348 contract->setActorIds(std::move(actorIds));
349 auto propertyIds = contract->propertyIds();
350 dedupStrings(propertyIds);
351 contract->setPropertyIds(std::move(propertyIds));
352 }
353 for (const auto& statement : out.catalog.statements()) {
354 if (!statement) continue;
355 auto transactionIds = statement->transactionIds();
356 dedupStrings(transactionIds);
357 statement->setTransactionIds(std::move(transactionIds));
358 }
359 for (const auto& annual : out.catalog.annuals()) {
360 if (!annual) continue;
361 auto analysisIds = annual->analysisIds();
362 dedupStrings(analysisIds);
363 annual->setAnalysisIds(std::move(analysisIds));
364 }
365
366 return out;
367}
368
369}
370
372public:
373 explicit Impl(Repositories repositories)
374 : repos(std::move(repositories)) {
375 }
376
378 bool strictValidation = false;
379};
380
382 : impl_(std::make_unique<Impl>(std::move(repos))) {
383}
384
386
388
390
393
394 document.catalog.setActors(loadCollection(impl_->repos.actors, [](const auto& repo) { return repo.getActors(); }));
395 document.catalog.setProperties(loadCollection(impl_->repos.properties, [](const auto& repo) { return repo.getProperties(); }));
396 document.catalog.setContracts(loadCollection(impl_->repos.contracts, [](const auto& repo) { return repo.getContracts(); }));
397 document.catalog.setStatements(loadCollection(impl_->repos.statements, [](const auto& repo) { return repo.getStatements(); }));
398 document.catalog.setTransactions(loadCollection(impl_->repos.transactions, [](const auto& repo) { return repo.getTransactions(); }));
399 document.catalog.setAnalyses(loadCollection(impl_->repos.analyses, [](const auto& repo) { return repo.getAnalyses(); }));
400 document.catalog.setAnnuals(loadCollection(impl_->repos.annuals, [](const auto& repo) { return repo.getAnnuals(); }));
401 document.workflow.importLogs = loadCollection(impl_->repos.importLogs, [](const auto& repo) { return repo.getImportLogs(); });
402 document.workflow.exportLogs = loadCollection(impl_->repos.exportLogs, [](const auto& repo) { return repo.getExportLogs(); });
403
404 document.workflow.statementDrafts = loadCollection(impl_->repos.statementDrafts, [](const auto& repo) { return repo.getStatementDrafts(); });
405
406 if (impl_->repos.transactionDrafts) {
407 document.workflow.transactionDrafts = impl_->repos.transactionDrafts->getTransactionDrafts();
408 }
409
410 rehydrate(document);
411 validate(document, impl_->strictValidation);
412
413 return document;
414}
415
417 const core::application::workspace::WorkspaceSessionState projected = prepareForSave(document);
418 validate(projected, impl_->strictValidation);
419
420 upsertCollection(impl_->repos.actors, projected.catalog.actors(), [](auto& repo, const auto& actor) { repo.upsertActor(actor); });
421 upsertCollection(impl_->repos.properties, projected.catalog.properties(), [](auto& repo, const auto& property) { repo.upsertProperty(property); });
422 upsertCollection(impl_->repos.contracts, projected.catalog.contracts(), [](auto& repo, const auto& contract) { repo.upsertContract(contract); });
423 upsertCollection(impl_->repos.statements, projected.catalog.statements(), [](auto& repo, const auto& statement) { repo.upsertStatement(statement); });
424 upsertCollection(impl_->repos.transactions, projected.catalog.transactions(), [](auto& repo, const auto& transaction) { repo.upsertTransaction(transaction); });
425 upsertCollection(impl_->repos.analyses, projected.catalog.analyses(), [](auto& repo, const auto& analysis) { repo.upsertAnalysis(analysis); });
426 upsertCollection(impl_->repos.annuals, projected.catalog.annuals(), [](auto& repo, const auto& annual) { repo.upsertAnnual(annual); });
427 replaceCollection(impl_->repos.importLogs,
428 projected.workflow.importLogs,
429 [](auto& repo) { repo.clearImportLogs(); },
430 [](auto& repo, const auto& item) { repo.upsertImportLog(item); });
431 replaceCollection(impl_->repos.exportLogs,
432 projected.workflow.exportLogs,
433 [](auto& repo) { repo.clearExportLogs(); },
434 [](auto& repo, const auto& item) { repo.upsertExportLog(item); });
435 replaceCollection(impl_->repos.statementDrafts,
436 projected.workflow.statementDrafts,
437 [](auto& repo) { repo.clearStatementDrafts(); },
438 [](auto& repo, const auto& item) { repo.upsertStatementDraft(item); });
439 replaceCollection(impl_->repos.transactionDrafts,
440 projected.workflow.transactionDrafts,
441 [](auto& repo) { repo.clearTransactionDrafts(); },
442 [](auto& repo, const auto& item) { repo.upsertTransactionDraft(item); });
443}
444
446 impl_->strictValidation = enabled;
447}
448
449}
Domain model for actors.
Domain model for persisted analysis definitions.
Domain model for annual aggregates.
Domain model for contracts.
Value object for contract type labels.
Small value object for normalized entity display names.
Repository port for core::domain::Actor persistence operations.
Repository port for core::domain::Analysis persistence operations.
Repository port for core::domain::Annual persistence operations.
Repository port for core::domain::Contract persistence operations.
Repository port for core::domain::Property persistence operations.
Repository port for core::domain::Statement persistence operations.
Repository port for transaction draft persistence operations.
Repository port for core::domain::Transaction persistence operations.
Domain model for properties.
Aggregates repository ports required by workspace state persistence.
Domain model for statements.
Domain model for transactions.
Internal mutable workspace session state for application services.
Loader/saver for internal workspace session state using repository abstractions.
Loads and saves aggregate workspace state via repository abstractions.
void setStrictValidation(bool enabled) noexcept
Enables or disables strict validation during load and save.
void save(const core::application::workspace::WorkspaceSessionState &document)
Projects, validates and persists workspace session state.
core::storage::RepositoryBundle Repositories
core::application::workspace::WorkspaceSessionState load()
Loads, rehydrates and validates workspace session state.
WorkspaceStateManager(Repositories repos)
Creates a manager over the supplied repository bundle.
static bool isValid(const std::string &value)
Checks whether a raw contract type is valid.
static bool isValid(const std::string &value)
Checks whether a raw name is valid.
EntityList< core::domain::Contract > ContractList
EntityList< core::domain::Transaction > TransactionList
void setAnnuals(AnnualList value)
Replaces all annuals in the catalog.
EntityList< core::domain::Annual > AnnualList
const PropertyList & properties() const noexcept
Returns all properties in the catalog.
void setAnalyses(AnalysisList value)
Replaces all analyses in the catalog.
EntityList< core::domain::Analysis > AnalysisList
void setProperties(PropertyList value)
Replaces all properties in the catalog.
void setStatements(StatementList value)
Replaces all statements in the catalog.
EntityList< core::domain::Property > PropertyList
const AnnualList & annuals() const noexcept
Returns all annuals in the catalog.
EntityList< core::domain::Statement > StatementList
const StatementList & statements() const noexcept
Returns all statements in the catalog.
const TransactionList & transactions() const noexcept
Returns all transactions in the catalog.
void setActors(ActorList value)
Replaces all actors in the catalog.
const ActorList & actors() const noexcept
Returns all actors in the catalog.
void setContracts(ContractList value)
Replaces all contracts in the catalog.
const ContractList & contracts() const noexcept
Returns all contracts in the catalog.
const AnalysisList & analyses() const noexcept
Returns all analyses in the catalog.
void setTransactions(TransactionList value)
Replaces all transactions in the catalog.
EntityList< core::domain::Actor > ActorList
Collects common core includes and aliases for precompiled-header builds.
void normalizeAliases(std::vector< Alias > &aliases)
Normalizes and deduplicates a list of aliases.
core::domain::catalog::WorkspaceCatalog catalog