From 4a3022201d08a6c4c5648c45903c3e0e77c440d5 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Tue, 18 Oct 2022 23:00:39 -0400 Subject: [PATCH 1/3] Add a template helper class for updating resources. This class holds a set of update functors indexed by the type and the version numbers of things that they process. --- doc/smtk.doxyfile.in | 1 + doc/userguide/common/index.rst | 1 + doc/userguide/common/updaters.rst | 16 ++ smtk/common/CMakeLists.txt | 2 + smtk/common/testing/cxx/CMakeLists.txt | 1 + .../testing/cxx/UnitTestUpdateFactory.cxx | 202 ++++++++++++++++ smtk/common/update/Factory.h | 227 ++++++++++++++++++ smtk/doc.h | 5 + 8 files changed, 455 insertions(+) create mode 100644 doc/userguide/common/updaters.rst create mode 100644 smtk/common/testing/cxx/UnitTestUpdateFactory.cxx create mode 100644 smtk/common/update/Factory.h diff --git a/doc/smtk.doxyfile.in b/doc/smtk.doxyfile.in index 207cc8f1c5..e8bc9d623b 100644 --- a/doc/smtk.doxyfile.in +++ b/doc/smtk.doxyfile.in @@ -739,6 +739,7 @@ INPUT = \ "@smtk_SOURCE_DIR@/smtk/simulation" \ "@smtk_SOURCE_DIR@/smtk/project" \ "@smtk_SOURCE_DIR@/smtk/common" \ + "@smtk_SOURCE_DIR@/smtk/common/update" \ "@smtk_SOURCE_DIR@/smtk/task" \ "@smtk_SOURCE_DIR@/smtk/task/adaptor" \ "@smtk_SOURCE_DIR@/smtk/task/json" \ diff --git a/doc/userguide/common/index.rst b/doc/userguide/common/index.rst index a4263d5c8c..a8ce20badc 100644 --- a/doc/userguide/common/index.rst +++ b/doc/userguide/common/index.rst @@ -17,3 +17,4 @@ patterns are defined in SMTK's `common` directory. threadpool.rst typecontainer.rst typemap.rst + updaters.rst diff --git a/doc/userguide/common/updaters.rst b/doc/userguide/common/updaters.rst new file mode 100644 index 0000000000..51c36f50cc --- /dev/null +++ b/doc/userguide/common/updaters.rst @@ -0,0 +1,16 @@ +.. _smtk-updaters: + +Update factory +============== + +SMTK resources – particularly the attribute resource – can store +data that presumes or specifies a developer-provided schema in +addition to user-provided content. +As a project matures, developers must make changes to the schema +but wish to provide users with a path to migrate their content +to the new schema rather than abandoning it. + +The :smtk:`update::Factory ` provides developers +with a way to register functions that can accept resources in +an old schema and copy it into a resource with a new schema, +adapting the user's content as needed. diff --git a/smtk/common/CMakeLists.txt b/smtk/common/CMakeLists.txt index ceb1aaad37..f6acd0a0e1 100644 --- a/smtk/common/CMakeLists.txt +++ b/smtk/common/CMakeLists.txt @@ -67,6 +67,8 @@ set(commonHeaders WeakReferenceWrapper.h testing/cxx/helpers.h + update/Factory.h + ${CMAKE_CURRENT_BINARY_DIR}/Version.h ) diff --git a/smtk/common/testing/cxx/CMakeLists.txt b/smtk/common/testing/cxx/CMakeLists.txt index 1ebb444347..205d84cbc3 100644 --- a/smtk/common/testing/cxx/CMakeLists.txt +++ b/smtk/common/testing/cxx/CMakeLists.txt @@ -45,6 +45,7 @@ set(unit_tests UnitTestTypeContainer.cxx UnitTestTypeMap.cxx UnitTestTypeName.cxx + UnitTestUpdateFactory.cxx UnitTestVersionNumber.cxx UnitTestVisit.cxx ) diff --git a/smtk/common/testing/cxx/UnitTestUpdateFactory.cxx b/smtk/common/testing/cxx/UnitTestUpdateFactory.cxx new file mode 100644 index 0000000000..82a39207ac --- /dev/null +++ b/smtk/common/testing/cxx/UnitTestUpdateFactory.cxx @@ -0,0 +1,202 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#include "smtk/common/TypeName.h" +#include "smtk/common/testing/cxx/helpers.h" +#include "smtk/common/update/Factory.h" + +#include + +namespace +{ +class Blob +{ +public: + virtual ~Blob() = default; +}; + +class OldBlob : public Blob +{ +public: + OldBlob() = default; + ~OldBlob() override = default; + int BlobVersion = 1; + int Data = 2; +}; + +class NewBlob : public Blob +{ +public: + NewBlob() = default; + ~NewBlob() override = default; + int BlobVersion = 10; + int Data = 5; +}; + +class ReallyNewBlob : public Blob +{ +public: + ReallyNewBlob() = default; + ~ReallyNewBlob() override = default; + int BlobVersion = 100; + int Data = 20; +}; + +} // anonymous namespace + +int UnitTestUpdateFactory(int /*unused*/, char** const /*unused*/) +{ + using BlobUpdateSignature = std::function; + using BlobUpdateFactory = smtk::common::update::Factory; + BlobUpdateFactory updaters; + bool didRegister; + std::string oldBlobType = smtk::common::typeName(); + std::string newBlobType = smtk::common::typeName(); + std::string reallyNewBlobType = smtk::common::typeName(); + + didRegister = updaters.registerUpdater("", 1, 2, 3, [](const Blob*, Blob*) { return false; }); + test(!didRegister, "Should refuse to register an empty target."); + + didRegister = + updaters.registerUpdater(oldBlobType, 1, 0, 3, [](const Blob*, Blob*) { return false; }); + test(!didRegister, "Should refuse to register an invalid input-version range."); + + didRegister = + updaters.registerUpdater(oldBlobType, 1, 2, 2, [](const Blob*, Blob*) { return false; }); + test(!didRegister, "Should refuse to register an invalid output version."); + + BlobUpdateSignature nullUpdater; + didRegister = updaters.registerUpdater(oldBlobType, 1, 2, 3, nullUpdater); + test(!didRegister, "Should refuse to register an null updater."); + + didRegister = + updaters.registerUpdater(oldBlobType, 1, 2, 5, [](const Blob* fromBase, Blob* toBase) { + const auto* from = dynamic_cast(fromBase); + auto* to = dynamic_cast(toBase); + if (!from || !to) + { + return false; + } + + std::cout << "Updating OldBlob v1–2 to NewBlob v5\n"; + to->Data = 3 * from->Data - 1; + to->BlobVersion = 5; + return true; + }); + test(didRegister, "Should register an valid updater (1)."); + + didRegister = + updaters.registerUpdater(oldBlobType, 1, 3, 10, [](const Blob* fromBase, Blob* toBase) { + const auto* from = dynamic_cast(fromBase); + auto* to = dynamic_cast(toBase); + if (!from || !to) + { + return false; + } + + std::cout << "Updating OldBlob v1–3 to NewBlob v10\n"; + to->Data = 2 * from->Data + 1; + to->BlobVersion = 10; + return true; + }); + test(didRegister, "Should register an valid updater (2)."); + + didRegister = + updaters.registerUpdater(newBlobType, 10, 11, 12, [](const Blob* fromBase, Blob* toBase) { + const auto* from = dynamic_cast(fromBase); + auto* to = dynamic_cast(toBase); + if (!from || !to) + { + return false; + } + + std::cout << "Updating NewBlob v10–11 to ReallyNewBlob v100\n"; + to->Data = 4 * from->Data + 2; + to->BlobVersion = 12; + return true; + }); + + OldBlob oldBlob1; + OldBlob oldBlob2; + OldBlob oldBlob3; + NewBlob newBlob1; + NewBlob newBlob2; + NewBlob newBlob3; + ReallyNewBlob reallyNewBlob; + bool didUpdate = false; + + // Test a valid updater to NewBlob's default version. + if ( + const auto& updater = updaters.find( + smtk::common::typeName(), oldBlob1.BlobVersion, newBlob1.BlobVersion)) + { + didUpdate = updater.Update(&oldBlob1, &newBlob1); + test(didUpdate, "Expected to update valid inputs."); + test(newBlob1.BlobVersion == 10, "Expected version 10."); + test(newBlob1.Data == 5, "Expected data 5 (perhaps bad updater selected?)."); + } + + // Test a valid updater to a non-default NewBlob version. + if ( + const auto& updater = + updaters.find(smtk::common::typeName(), oldBlob2.BlobVersion, 5)) + { + didUpdate = updater.Update(&oldBlob1, &newBlob1); + test(didUpdate, "Expected to update valid inputs."); + test(newBlob1.BlobVersion == 5, "Expected version 10."); + test(newBlob1.Data == 5, "Expected data 5 (perhaps bad updater selected?)."); + } + + // Test that updating fails when no updater is present. + if ( + const auto& updater = updaters.find( + smtk::common::typeName(), + newBlob1.BlobVersion, + reallyNewBlob.BlobVersion)) + { + test(!updater, "Returned updater should be invalid."); + test(false, "No updaters should match target name and version."); + } + + auto oldVersionsIn = updaters.canAccept(smtk::common::typeName()); + auto newVersionsOut = updaters.canProduce(smtk::common::typeName()); + std::cout << "OldBlob updaters accept versions:"; + for (const auto& vv : oldVersionsIn) + { + std::cout << " " << vv; + } + std::cout << "\n"; + std::cout << "OldBlob updaters produce versions:"; + for (const auto& vv : newVersionsOut) + { + std::cout << " " << vv; + } + std::cout << "\n"; + test(oldVersionsIn == std::set{ 1, 2, 3 }, "Unexpected old version numbers in."); + test(newVersionsOut == std::set{ 5, 10 }, "Unexpected new version numbers out."); + + auto newVersionsIn = updaters.canAccept(smtk::common::typeName()); + auto btrVersionsOut = updaters.canProduce(smtk::common::typeName()); + std::cout << "NewBlob updaters accept versions:"; + for (const auto& vv : newVersionsIn) + { + std::cout << " " << vv; + } + std::cout << "\n"; + std::cout << "NewBlob updaters produce versions:"; + for (const auto& vv : btrVersionsOut) + { + std::cout << " " << vv; + } + std::cout << "\n"; + test(newVersionsIn == std::set{ 10, 11 }, "Unexpected new version numbers in."); + test(btrVersionsOut == std::set{ 12 }, "Unexpected really new version numbers out."); + + return 0; +} diff --git a/smtk/common/update/Factory.h b/smtk/common/update/Factory.h new file mode 100644 index 0000000000..237f1c01db --- /dev/null +++ b/smtk/common/update/Factory.h @@ -0,0 +1,227 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= + +#ifndef smtk_common_update_Factory_h +#define smtk_common_update_Factory_h + +#include "smtk/string/Token.h" + +#include // for std::size_t +#include +#include + +namespace smtk +{ +namespace common +{ +namespace update +{ + +/**\brief Common class to hold a dictionary of update methods + * for various elements in SMTK. + * + * The template parameter is the signature of a function to which + * a target element and output object are passed. + * + * The template parameter must name a type with a default constructor + * that produces an object which can be implicitly converted to a + * false-like value. + * For example, an item updater might use an UpdaterSignature + * of std::function; the default constructor + * produces an empty functor that is implicitly converted to nullptr. + * This property is used to produce an invalid factory Entry when + * no updater exists. + */ +template +class Factory +{ +public: + /**\brief An entry in the factory holding an update method and + * all the metadata required to index the things the method + * applies to. + */ + struct Entry + { + Entry() = default; + Entry( + std::size_t appliesToVersionMin, + std::size_t appliesToVersionMax, + std::size_t producesVersion, + UpdaterSignature update) + : AppliesToVersionMin(appliesToVersionMin) + , AppliesToVersionMax(appliesToVersionMax) + , ProducesVersion(producesVersion) + , Update(update) + { + } + /// The lowest revision of the input element this updater applies to. + std::size_t AppliesToVersionMin = 0; + /// The highest revision of the input element this updater applies to. + std::size_t AppliesToVersionMax = 0; + /// The version of an element produced by invoking the updater on a target. + std::size_t ProducesVersion = 0; + /// The method to invoke on a target to update it. + UpdaterSignature Update; + + /// True when an entry has a valid updater and revision range. + bool isValid() const; + + /// An entry is truthy when it is valid. + operator bool() const { return this->isValid(); } + /// A comparator for insertion into ordered containers. + bool operator<(const Entry& other) const; + }; + + /// Return an entry with the appropriate updater (or an invalid entry). + const Entry& find( + smtk::string::Token elementSpec, + std::size_t resultElementVersion, + std::size_t inputElementVersion); + + /// Given a target, return the set of versions which updaters can produce as output. + std::set canProduce(smtk::string::Token elementSpec) const; + + /// Given a target, return the set of versions which updaters can accept as input. + std::set canAccept(smtk::string::Token elementSpec) const; + + bool registerUpdater( + smtk::string::Token target, + std::size_t appliesToVersionMin, + std::size_t appliesToVersionMax, + std::size_t producesVersion, + UpdaterSignature updater); + +protected: + std::unordered_map> m_entries; +}; + +template +bool Factory::Entry::isValid() const +{ + bool valid = this->Update && this->AppliesToVersionMin <= this->AppliesToVersionMax && + this->ProducesVersion > this->AppliesToVersionMax; + return valid; +} + +template +bool Factory::Entry::operator<(const Entry& other) const +{ + bool isLessThan = + (this->ProducesVersion < other.ProducesVersion || + (this->ProducesVersion == other.ProducesVersion && + (this->AppliesToVersionMin < other.AppliesToVersionMin || + (this->AppliesToVersionMin == other.AppliesToVersionMin && + (this->AppliesToVersionMax < other.AppliesToVersionMax || + (this->AppliesToVersionMax == other.AppliesToVersionMax && + &this->Update < &other.Update)))))); + return isLessThan; +} + +template +const typename Factory::Entry& Factory::find( + smtk::string::Token elementSpec, + std::size_t resultElementVersion, + std::size_t inputElementVersion) +{ + static thread_local Factory::Entry invalid; + auto eit = m_entries.find(elementSpec); + if (eit == m_entries.end()) + { + return invalid; + } + // For now, just return the first match that meets requirements. + // In the future, we may get fancier. + for (const auto& updater : eit->second) + { + if (!updater.isValid()) + { + continue; + } + if ( + updater.AppliesToVersionMin <= inputElementVersion && + updater.AppliesToVersionMax >= inputElementVersion && + updater.ProducesVersion == resultElementVersion) + { + return updater; + } + } + return invalid; +} + +/// Given a target, return the set of versions which updaters can produce as output. +template +std::set Factory::canProduce(smtk::string::Token elementSpec) const +{ + std::set versions; + auto eit = m_entries.find(elementSpec); + if (eit == m_entries.end()) + { + return versions; + } + for (const auto& updater : eit->second) + { + if (!updater.isValid()) + { + continue; + } + versions.insert(updater.ProducesVersion); + } + return versions; +} + +/// Given a target, return the set of versions which updaters can accept as input. +template +std::set Factory::canAccept(smtk::string::Token elementSpec) const +{ + std::set versions; + auto eit = m_entries.find(elementSpec); + if (eit == m_entries.end()) + { + return versions; + } + for (const auto& updater : eit->second) + { + if (!updater.isValid()) + { + continue; + } + for (std::size_t vv = updater.AppliesToVersionMin; vv <= updater.AppliesToVersionMax; ++vv) + { + versions.insert(vv); + } + } + return versions; +} + +template +bool Factory::registerUpdater( + smtk::string::Token target, + std::size_t appliesToVersionMin, + std::size_t appliesToVersionMax, + std::size_t producesVersion, + UpdaterSignature update) +{ + // Refuse to insert ill-formed metadata. + if ( + !update || target.data().empty() || appliesToVersionMin > appliesToVersionMax || + appliesToVersionMax >= producesVersion) + { + return false; + } + Entry ee(appliesToVersionMin, appliesToVersionMax, producesVersion, update); + m_entries[target].insert(ee); + return true; +} + +} // namespace update +} // namespace common +} // namespace smtk + +#endif // smtk_common_update_Factory_h diff --git a/smtk/doc.h b/smtk/doc.h index d32fb6ca01..4fb659ecc0 100644 --- a/smtk/doc.h +++ b/smtk/doc.h @@ -38,7 +38,12 @@ namespace smtk */ namespace common { +/**\brief Classes to aid developers in migrating user content through schema upgrades. + */ +namespace update +{ } +} // namespace common /**\brief A common base class for resources (data stored in files) and tools to manage them. * -- GitLab From 9b559819108d3b9e69893d85c626b8e929af9d47 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Tue, 18 Oct 2022 23:46:46 -0400 Subject: [PATCH 2/3] Add a template type and version number to attribute::Resource. The type and version number are for use by SBT developers who need to version the attribute and item definitions for use in migrating instanced attributes to a newer SBT schema than was used to create a resource in the first place. Note that the changes to the PEGTL parsing code are required once the attribute resource includes `smtk/string/Token.h` because that defines a namespace (`string`) which makes the PEGTL `string<>` template ambiguous. Use the macro PEGTL provides instead. --- smtk/attribute/Resource.cxx | 20 ++++++++++++++++++++ smtk/attribute/Resource.h | 21 +++++++++++++++++++++ smtk/attribute/filter/Attribute.h | 2 +- smtk/attribute/filter/Grammar.h | 2 +- smtk/io/XmlDocV6Parser.cxx | 23 +++++++++++++++++++++++ smtk/io/XmlDocV6Parser.h | 3 +++ smtk/model/FilterGrammar.h | 2 +- 7 files changed, 70 insertions(+), 3 deletions(-) diff --git a/smtk/attribute/Resource.cxx b/smtk/attribute/Resource.cxx index a3de0dbe0e..8193ead0ca 100644 --- a/smtk/attribute/Resource.cxx +++ b/smtk/attribute/Resource.cxx @@ -1106,6 +1106,26 @@ smtk::attribute::Resource::GuardedLinks Resource::guardedLinks() return GuardedLinks(this->mutex(), this->links()); } +bool Resource::setTemplateType(const smtk::string::Token& templateType) +{ + if (m_templateType == templateType) + { + return false; + } + m_templateType = templateType; + return true; +} + +bool Resource::setTemplateVersion(std::size_t templateVersion) +{ + if (m_templateVersion == templateVersion || templateVersion == 0) + { + return false; + } + m_templateVersion = templateVersion; + return true; +} + void Resource::setActiveCategoriesEnabled(bool mode) { m_activeCategoriesEnabled = mode; diff --git a/smtk/attribute/Resource.h b/smtk/attribute/Resource.h index 204bfca605..64eaa8e866 100644 --- a/smtk/attribute/Resource.h +++ b/smtk/attribute/Resource.h @@ -33,6 +33,8 @@ #include "smtk/attribute/ItemDefinition.h" #include "smtk/attribute/SymbolDependencyStorage.h" +#include "smtk/string/Token.h" + #include "smtk/view/Configuration.h" #include @@ -378,6 +380,22 @@ public: std::mutex& mutex() const { return m_mutex; } + /// Set/get the "type" of a resource's template. + /// + /// A resource template-type is not required, but if present it can be used to + /// register updaters for migrating from an old template to a newer version. + virtual bool setTemplateType(const smtk::string::Token& templateType); + const smtk::string::Token& templateType() const { return m_templateType; } + + /// Set/get the version of the template this instance of the resource is based upon. + /// + /// If non-zero, this number indicates the version number of the + /// template (i.e., SBT file) the definitions in the current resource + /// are drawn from. It is used during the update process to determine + /// which updaters are applicable. + virtual bool setTemplateVersion(std::size_t templateVersion); + std::size_t templateVersion() const { return m_templateVersion; } + protected: Resource(const smtk::common::UUID& myID, smtk::resource::ManagerPtr manager); Resource(smtk::resource::ManagerPtr manager = nullptr); @@ -426,6 +444,9 @@ protected: std::string m_defaultAttNameSeparator = "-"; + smtk::string::Token m_templateType; + std::size_t m_templateVersion = 0; + private: mutable std::mutex m_mutex; }; diff --git a/smtk/attribute/filter/Attribute.h b/smtk/attribute/filter/Attribute.h index 3081aa5ba5..25ed88ba5d 100644 --- a/smtk/attribute/filter/Attribute.h +++ b/smtk/attribute/filter/Attribute.h @@ -51,7 +51,7 @@ struct AttributeTypeSpec seq< TAO_PEGTL_ISTRING("type"), star, - string<'='>, + TAO_PEGTL_ISTRING("="), star, sor< pad, space>, diff --git a/smtk/attribute/filter/Grammar.h b/smtk/attribute/filter/Grammar.h index 57a62ee2f2..f5bb1559c2 100644 --- a/smtk/attribute/filter/Grammar.h +++ b/smtk/attribute/filter/Grammar.h @@ -42,7 +42,7 @@ struct SMTKCORE_EXPORT Grammar smtk::resource::filter::Property>::Grammar, AttributeTypeSpec::Grammar>, space>, - string<','>>>>> + TAO_PEGTL_ISTRING(",")>>>> { }; } // namespace filter diff --git a/smtk/io/XmlDocV6Parser.cxx b/smtk/io/XmlDocV6Parser.cxx index 2893fe3338..246dbbcba9 100644 --- a/smtk/io/XmlDocV6Parser.cxx +++ b/smtk/io/XmlDocV6Parser.cxx @@ -66,6 +66,29 @@ bool XmlDocV6Parser::canParse(pugi::xml_node& node) return versionNum == 6; } +void XmlDocV6Parser::process(pugi::xml_node& rootNode) +{ + pugi::xml_attribute xatt; + + xatt = rootNode.attribute("TemplateType"); + if (xatt) + { + smtk::string::Token templateType = xatt.value(); + m_resource->setTemplateType(templateType); + } + + unsigned long long tmp = 0; + xatt = rootNode.attribute("TemplateVersion"); + if (xatt) + { + tmp = xatt.as_ullong(tmp); + } + std::size_t templateVersion = static_cast(tmp); + m_resource->setTemplateVersion(templateVersion); + + XmlDocV5Parser::process(rootNode); +} + void XmlDocV6Parser::processCategories( xml_node& node, Categories::Set& catSet, diff --git a/smtk/io/XmlDocV6Parser.h b/smtk/io/XmlDocV6Parser.h index 1d59f2a54b..9d338f4784 100644 --- a/smtk/io/XmlDocV6Parser.h +++ b/smtk/io/XmlDocV6Parser.h @@ -31,6 +31,9 @@ public: static bool canParse(pugi::xml_node& node); static bool canParse(pugi::xml_document& doc); + using XmlDocV5Parser::process; + void process(pugi::xml_node& rootNode) override; + protected: void processCategories( pugi::xml_node& node, diff --git a/smtk/model/FilterGrammar.h b/smtk/model/FilterGrammar.h index d0a6285bdd..374e68ee6b 100644 --- a/smtk/model/FilterGrammar.h +++ b/smtk/model/FilterGrammar.h @@ -152,7 +152,7 @@ struct grammar_for typename property_traits::name, opt, - opt, space>, typename property_traits::sequence>>>>, + opt, typename property_traits::sequence>>>>, space> { }; -- GitLab From f92ada14e72e3c2624f9712575ece2cd0942449a Mon Sep 17 00:00:00 2001 From: David Thompson Date: Wed, 19 Oct 2022 10:13:19 -0400 Subject: [PATCH 3/3] Add an `smtk::attribute::Manager` with updaters. This employs the `smtk::common::update::Factory` in the context of updating attribute-system resources, attributes, and items. --- .../notes/attribute-update-manager.rst | 18 ++ doc/userguide/attribute/concepts.rst | 15 ++ doc/userguide/attribute/file-syntax.rst | 11 +- doc/userguide/common/updaters.rst | 14 +- smtk/attribute/CMakeLists.txt | 4 + smtk/attribute/Registrar.cxx | 2 + smtk/attribute/UpdateManager.h | 108 ++++++++++ smtk/attribute/json/jsonResource.cxx | 16 ++ .../pybind11/PybindAttributeModule4.cxx | 7 +- smtk/attribute/pybind11/PybindResource.h | 2 + smtk/attribute/pybind11/PybindUpdateManager.h | 58 ++++++ smtk/attribute/testing/cxx/CMakeLists.txt | 1 + .../attribute/testing/cxx/unitItemUpdater.cxx | 190 ++++++++++++++++++ smtk/attribute/testing/python/CMakeLists.txt | 1 + smtk/attribute/testing/python/itemUpdater.py | 128 ++++++++++++ .../attribute/update/AttributeUpdateFactory.h | 46 +++++ smtk/attribute/update/ItemUpdateFactory.h | 45 +++++ smtk/attribute/update/ResourceUpdateFactory.h | 45 +++++ 18 files changed, 701 insertions(+), 10 deletions(-) create mode 100644 doc/release/notes/attribute-update-manager.rst create mode 100644 smtk/attribute/UpdateManager.h create mode 100644 smtk/attribute/pybind11/PybindUpdateManager.h create mode 100644 smtk/attribute/testing/cxx/unitItemUpdater.cxx create mode 100644 smtk/attribute/testing/python/itemUpdater.py create mode 100644 smtk/attribute/update/AttributeUpdateFactory.h create mode 100644 smtk/attribute/update/ItemUpdateFactory.h create mode 100644 smtk/attribute/update/ResourceUpdateFactory.h diff --git a/doc/release/notes/attribute-update-manager.rst b/doc/release/notes/attribute-update-manager.rst new file mode 100644 index 0000000000..f1ef7ae888 --- /dev/null +++ b/doc/release/notes/attribute-update-manager.rst @@ -0,0 +1,18 @@ +Attribute update manager +------------------------ + +The attribute system now has an update manager to aid +you in migrating resources from one schema version +(i.e., template) to another. +See the :ref:`smtk-updaters` update factory documentation +for the basic pattern used to register handlers for +items, attributes, or even whole resource types. + +As part of this change, each attribute resource now has +a ``templateType()`` and a ``templateVersion()`` method +to identify the schema from which it is derived. +The values are provided by the SimBuilder Template (SBT) file +used as the prototype for the resource. +Workflow designers should update template files with +a template type and version in order to support future +migration. diff --git a/doc/userguide/attribute/concepts.rst b/doc/userguide/attribute/concepts.rst index 013c84d281..ee498291b8 100644 --- a/doc/userguide/attribute/concepts.rst +++ b/doc/userguide/attribute/concepts.rst @@ -252,3 +252,18 @@ When SMTK generates a user interface for the attribute above, the "construction method" value is represented as a tabbed widget with 1 tab for each of the "Structure" sections above. The default tab will be "2 points". + + +Migration to newer schema +------------------------- + +Over time, the schema used by an attribute resource may need to be +modified to fix issues or add features. +SMTK provides some support to aid you in migrating existing attribute +resources from an old schema to a new one while retaining as much +user-provided information as possible. +This is intended to be accomplished by copying attribute instances and +other information from the existing resource into a new resource whose +definitions are from a newer schema. +See the ``itemUpdater`` test for an example how to register +functions or python scripts to help with this process. diff --git a/doc/userguide/attribute/file-syntax.rst b/doc/userguide/attribute/file-syntax.rst index 09d6ede602..442b884c4a 100644 --- a/doc/userguide/attribute/file-syntax.rst +++ b/doc/userguide/attribute/file-syntax.rst @@ -23,9 +23,18 @@ Attributes that can be included in this XML Element. - Boolean value that indicates if the Attribute Resource should be automatically displayed in an Attribute Editor when loaded into memory. - (Optional - default is false which means the Resource will not automatically be + (Optional – default is false which means the Resource will not automatically be displayed) + * - TemplateType + - A string providing a name for the workflow this attribute describes. + + (Optional – the default is an empty string.) + + * - TemplateVersion + - An integer describing the version number of the schema for this attribute + resource. The default value is 0. + This element can contain the following optional children XML Elements: - Includes : used for including additional attribute files (see `Includes Section`_) diff --git a/doc/userguide/common/updaters.rst b/doc/userguide/common/updaters.rst index 51c36f50cc..7aadeb665c 100644 --- a/doc/userguide/common/updaters.rst +++ b/doc/userguide/common/updaters.rst @@ -4,13 +4,13 @@ Update factory ============== SMTK resources – particularly the attribute resource – can store -data that presumes or specifies a developer-provided schema in +data that presumes or specifies a designer-provided schema in addition to user-provided content. -As a project matures, developers must make changes to the schema -but wish to provide users with a path to migrate their content -to the new schema rather than abandoning it. +As a project matures, developers and workflow designers must make +changes to the schema but wish to provide users with a path to migrate +their content to the new schema rather than abandoning it. -The :smtk:`update::Factory ` provides developers -with a way to register functions that can accept resources in -an old schema and copy it into a resource with a new schema, +The :smtk:`update::Factory ` provides +developers with a way to register functions that can accept resources +in an old schema and copy it into a resource with a new schema, adapting the user's content as needed. diff --git a/smtk/attribute/CMakeLists.txt b/smtk/attribute/CMakeLists.txt index 0ecf06361f..585ae6d0ae 100644 --- a/smtk/attribute/CMakeLists.txt +++ b/smtk/attribute/CMakeLists.txt @@ -133,6 +133,7 @@ set(attributeHeaders SymbolDependencyStorage.h Tag.h UnsetValueError.h + UpdateManager.h ValueItem.h ValueItemDefinition.h ValueItemDefinitionTemplate.h @@ -141,6 +142,9 @@ set(attributeHeaders VoidItemDefinition.h filter/Attribute.h filter/Grammar.h + update/AttributeUpdateFactory.h + update/ItemUpdateFactory.h + update/ResourceUpdateFactory.h utility/Queries.h ) diff --git a/smtk/attribute/Registrar.cxx b/smtk/attribute/Registrar.cxx index f2757f325c..3e9871ea4a 100644 --- a/smtk/attribute/Registrar.cxx +++ b/smtk/attribute/Registrar.cxx @@ -15,6 +15,7 @@ #include "smtk/attribute/InfixExpressionEvaluator.h" #include "smtk/attribute/ItemDefinitionManager.h" #include "smtk/attribute/Resource.h" +#include "smtk/attribute/UpdateManager.h" #include "smtk/attribute/operators/Associate.h" #include "smtk/attribute/operators/Dissociate.h" @@ -47,6 +48,7 @@ typedef std::tuple O void Registrar::registerTo(const smtk::common::Managers::Ptr& managers) { + managers->insert(smtk::attribute::UpdateManager::create()); managers->insert(smtk::attribute::ItemDefinitionManager::create()); managers->insert(smtk::attribute::AssociationRuleManager::create()); managers->insert(smtk::attribute::EvaluatorManager::create()); diff --git a/smtk/attribute/UpdateManager.h b/smtk/attribute/UpdateManager.h new file mode 100644 index 0000000000..46ae4bafaf --- /dev/null +++ b/smtk/attribute/UpdateManager.h @@ -0,0 +1,108 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= + +#ifndef smtk_attribute_UpdateManager_h +#define smtk_attribute_UpdateManager_h + +#include "smtk/attribute/update/AttributeUpdateFactory.h" +#include "smtk/attribute/update/ItemUpdateFactory.h" +#include "smtk/attribute/update/ResourceUpdateFactory.h" + +#include "smtk/string/Token.h" + +#include + +namespace smtk +{ +namespace attribute +{ + +/**\brief Manage the attribute system. + * + */ +class SMTKCORE_EXPORT UpdateManager +{ +public: + smtkTypeMacroBase(smtk::attribute::UpdateManager); + smtkCreateMacro(smtk::attribute::UpdateManager); + virtual ~UpdateManager() = default; + + /// Return a factory holding methods to update entire resources + /// at a time (i.e., those that do not have a simple mapping from + /// old to new attribute definitions and instances). + update::ResourceUpdateFactory& resourceUpdaters() { return m_resourceUpdaters; } + const update::ResourceUpdateFactory& resourceUpdaters() const { return m_resourceUpdaters; } + + /// Return a factory holding methods to update entire attributes + /// at a time (i.e., those that do not have a simple mapping from + /// old to new item definitions and instances). + /// + /// The input parameter is the name of a resource's template-type. + /// \sa Resource::templateType() + update::AttributeUpdateFactory& attributeUpdaters(const smtk::string::Token& resourceTemplate) + { + // This will create a new factory if none exist. + return m_attributeUpdaters[resourceTemplate]; + } + const update::AttributeUpdateFactory& attributeUpdaters( + const smtk::string::Token& resourceTemplate) const + { + // This will return an immutably blank factory if there is no match. + static thread_local update::AttributeUpdateFactory blank; + auto it = m_attributeUpdaters.find(resourceTemplate); + if (it == m_attributeUpdaters.end()) + { + return blank; + } + return it->second; + } + + /// Return a factory holding methods to update items of an attribute + /// (i.e., those that do not have a simple mapping from old to new item definitions). + /// + /// The input parameters are the name of a resource's template-type + /// and the type of an attribute::Definition. + update::ItemUpdateFactory& itemUpdaters( + smtk::string::Token resourceTemplate, + smtk::string::Token attributeType) + { + // This will create a new factory if none exist. + auto key = std::make_pair(resourceTemplate, attributeType); + return m_itemUpdaters[key]; + } + const update::ItemUpdateFactory& itemUpdaters( + smtk::string::Token resourceTemplate, + smtk::string::Token attributeType) const + { + auto key = std::make_pair(resourceTemplate, attributeType); + // This will return an immutably blank factory if there is no match. + static thread_local update::ItemUpdateFactory blank; + auto it = m_itemUpdaters.find(key); + if (it == m_itemUpdaters.end()) + { + return blank; + } + return it->second; + } + +protected: + using AttributeUpdateKey = smtk::string::Token; + using ItemUpdateKey = std::pair; + UpdateManager() = default; + + update::ResourceUpdateFactory m_resourceUpdaters; + std::map m_attributeUpdaters; + std::map m_itemUpdaters; +}; + +} // namespace attribute +} // namespace smtk + +#endif // smtk_attribute_UpdateManager_h diff --git a/smtk/attribute/json/jsonResource.cxx b/smtk/attribute/json/jsonResource.cxx index 808818a614..44dae3225d 100644 --- a/smtk/attribute/json/jsonResource.cxx +++ b/smtk/attribute/json/jsonResource.cxx @@ -45,6 +45,11 @@ SMTKCORE_EXPORT void to_json(json& j, const smtk::attribute::ResourcePtr& res) j["version"] = "6.0"; j["IsPrivate"] = res->isPrivate(); j["NameSeparator"] = res->defaultNameSeparator(); + if (res->templateType().id() && !res->templateType().data().empty()) + { + j["TemplateType"] = res->templateType().data(); + j["TemplateVersion"] = res->templateVersion(); + } // Write out the active category information if (!res->activeCategories().empty()) { @@ -348,6 +353,17 @@ SMTKCORE_EXPORT void from_json(const json& j, smtk::attribute::ResourcePtr& res) resource->properties().get()["smtk.attribute_panel.display_hint"] = true; } + auto jtt = j.find("TemplateType"); + if (jtt != j.end()) + { + res->setTemplateType(jtt->get()); + auto jtv = j.find("TemplateVersion"); + if (jtv != j.end()) + { + res->setTemplateVersion(jtv->get()); + } + } + // Set Private State auto isPrivateResult = j.find("IsPrivate"); // if we're reading in an older attribute resource, default value to true diff --git a/smtk/attribute/pybind11/PybindAttributeModule4.cxx b/smtk/attribute/pybind11/PybindAttributeModule4.cxx index a7d8fb0a7a..b7bb1b8a55 100644 --- a/smtk/attribute/pybind11/PybindAttributeModule4.cxx +++ b/smtk/attribute/pybind11/PybindAttributeModule4.cxx @@ -14,10 +14,11 @@ SMTK_THIRDPARTY_PRE_INCLUDE #include SMTK_THIRDPARTY_POST_INCLUDE -#include "smtk/attribute/Resource.h" #include "smtk/attribute/Attribute.h" -#include "smtk/attribute/Item.h" #include "smtk/attribute/Definition.h" +#include "smtk/attribute/Item.h" +#include "smtk/attribute/Resource.h" +#include "smtk/attribute/UpdateManager.h" #include @@ -31,6 +32,7 @@ using PySharedPtrClass = py::class_, Args...>; #include "PybindExport.h" #include "PybindImport.h" #include "PybindRead.h" +#include "PybindUpdateManager.h" #include "PybindWrite.h" PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); @@ -47,4 +49,5 @@ void attributePart4(py::module& attribute) PySharedPtrClass< smtk::attribute::Export, smtk::operation::XMLOperation > smtk_attribute_Export = pybind11_init_smtk_attribute_Export(attribute); PySharedPtrClass< smtk::attribute::Read, smtk::operation::XMLOperation > smtk_attribute_Read = pybind11_init_smtk_attribute_Read(attribute); PySharedPtrClass< smtk::attribute::Write, smtk::operation::XMLOperation > smtk_attribute_Write = pybind11_init_smtk_attribute_Write(attribute); + PySharedPtrClass< smtk::attribute::UpdateManager> smtk_attribute_UpdateManager = pybind11_init_smtk_attribute_UpdateManager(attribute); } diff --git a/smtk/attribute/pybind11/PybindResource.h b/smtk/attribute/pybind11/PybindResource.h index baf79f106c..9c9a1e2f81 100644 --- a/smtk/attribute/pybind11/PybindResource.h +++ b/smtk/attribute/pybind11/PybindResource.h @@ -91,6 +91,8 @@ inline PySharedPtrClass< smtk::attribute::Resource> pybind11_init_smtk_attribute .def("setDefaultNameSeparator", &smtk::attribute::Resource::setDefaultNameSeparator, py::arg("separator")) .def("uniqueRoles", &smtk::attribute::Resource::uniqueRoles) .def("finalizeDefinitions", &smtk::attribute::Resource::finalizeDefinitions) + .def("templateType", [](smtk::attribute::Resource& rsrc) { return rsrc.templateType().data(); }) + .def("templateVersion", &smtk::attribute::Resource::templateVersion) .def("updateDerivedDefinitionIndexOffsets", &smtk::attribute::Resource::updateDerivedDefinitionIndexOffsets, py::arg("def")) .def("views", &smtk::attribute::Resource::views) .def("styles", &smtk::attribute::Resource::styles) diff --git a/smtk/attribute/pybind11/PybindUpdateManager.h b/smtk/attribute/pybind11/PybindUpdateManager.h new file mode 100644 index 0000000000..a2e356ff23 --- /dev/null +++ b/smtk/attribute/pybind11/PybindUpdateManager.h @@ -0,0 +1,58 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= + +#ifndef pybind_smtk_attribute_UpdateManager_h +#define pybind_smtk_attribute_UpdateManager_h + +#include +#include + +#include "smtk/attribute/UpdateManager.h" + +#include "smtk/attribute/Attribute.h" +#include "smtk/attribute/Definition.h" +#include "smtk/resource/Resource.h" + +namespace py = pybind11; + +inline PySharedPtrClass< smtk::attribute::UpdateManager> pybind11_init_smtk_attribute_UpdateManager(py::module &m) +{ + PySharedPtrClass< smtk::attribute::UpdateManager> instance(m, "UpdateManager"); + instance + .def_static("create", [](){ return smtk::attribute::UpdateManager::create(); }, py::return_value_policy::take_ownership) + .def("registerItemUpdater", []( + smtk::attribute::UpdateManager& manager, + const std::string& resourceTemplateType, const std::string& attributeType, + const std::string& itemPath, + std::size_t appliesToVersionMin, std::size_t appliesToVersionMax, + std::size_t producesVersion, smtk::attribute::update::ItemUpdater functor) + { + return + manager.itemUpdaters(resourceTemplateType, attributeType).registerUpdater( + itemPath, appliesToVersionMin, appliesToVersionMax, producesVersion, functor); + } + ) + .def("findItemUpdater", []( + smtk::attribute::UpdateManager& manager, + const std::string& resourceTemplateType, const std::string& attributeType, + const std::string& itemPath, + std::size_t producesVersion, + std::size_t appliesToVersion) + { + auto updater = + manager.itemUpdaters(resourceTemplateType, attributeType).find( + itemPath, producesVersion, appliesToVersion); + return updater.Update; + } + ); + return instance; +} + +#endif diff --git a/smtk/attribute/testing/cxx/CMakeLists.txt b/smtk/attribute/testing/cxx/CMakeLists.txt index ef779fdead..d216233d60 100644 --- a/smtk/attribute/testing/cxx/CMakeLists.txt +++ b/smtk/attribute/testing/cxx/CMakeLists.txt @@ -97,6 +97,7 @@ set(unit_tests unitInfixExpressionEvaluator.cxx unitIsRelevant.cxx unitIsValid.cxx + unitItemUpdater.cxx unitJsonItemDefinitions.cxx unitOptionalItems.cxx unitPassCategories.cxx diff --git a/smtk/attribute/testing/cxx/unitItemUpdater.cxx b/smtk/attribute/testing/cxx/unitItemUpdater.cxx new file mode 100644 index 0000000000..dbf191cb6b --- /dev/null +++ b/smtk/attribute/testing/cxx/unitItemUpdater.cxx @@ -0,0 +1,190 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#include "smtk/attribute/Definition.h" +#include "smtk/attribute/GroupItem.h" +#include "smtk/attribute/IntItem.h" +#include "smtk/attribute/Registrar.h" +#include "smtk/attribute/Resource.h" +#include "smtk/attribute/StringItem.h" +#include "smtk/attribute/UpdateManager.h" +#include "smtk/io/AttributeReader.h" +#include "smtk/io/Logger.h" + +#include "smtk/operation/Manager.h" +#include "smtk/plugin/Registry.h" +#include "smtk/resource/Manager.h" + +#include "smtk/common/testing/cxx/helpers.h" + +#include +#include + +const std::string attV1 = R"( + + + + + + + + + + + + + 37 + Dumb + + + + +)"; + +const std::string attV2 = R"( + + + + + + + + + + +)"; + +int unitItemUpdater(int /*unused*/, char* /*unused*/[]) +{ + using namespace smtk::attribute; + + auto managers = smtk::common::Managers::create(); + auto resourceManager = smtk::resource::Manager::create(); + auto operationManager = smtk::operation::Manager::create(); + managers->insert_or_assign(resourceManager); + managers->insert_or_assign(operationManager); + + // Register the resource manager to the operation manager (newly created + // resources will be automatically registered to the resource manager). + operationManager->registerResourceManager(resourceManager); + + auto attributeRegistry = smtk::plugin::addToManagers( + managers, + managers->get(), + managers->get()); + + // Register item updaters + // Note that this can happen before the items are defined because of the + // new TemplateType member of attribute::Resource. + auto updateManager = managers->get(); + ::test(!!updateManager, "Expected the attribute registrar would create an attribute manager."); + updateManager->itemUpdaters("Example", "Test") + .registerUpdater( + "SubjectiveIQ", 1, 9, 10, [](const Item& inRaw, Item& outRaw, smtk::io::Logger& log) { + const auto* in = dynamic_cast(&inRaw); + auto* out = dynamic_cast(&outRaw); + if (in && out) + { + smtkInfoMacro(log, "Converting SubjectiveIQ"); + out->setValue(in->value() + "er"); // Turn "Dumb" into "Dumber", "Smart" into "Smarter", … + return true; + } + return false; + }); + + updateManager->itemUpdaters("Example", "Test") + .registerUpdater( + "ObjectiveIQ", 1, 9, 10, [](const Item& inRaw, Item& outRaw, smtk::io::Logger& log) { + const auto* in = dynamic_cast(&inRaw); + auto* out = dynamic_cast(&outRaw); + if (in && out) + { + smtkInfoMacro(log, "Converting ObjectiveIQ"); + out->setValue(in->value() + 5); // New "IQ" is 5 above old "IQ." + return true; + } + return false; + }); + + auto logger = smtk::io::Logger::instance(); + smtk::io::AttributeReader reader; + + // Load an "old" attribute resource + auto attRsrc1 = smtk::attribute::Resource::create(); + bool err = reader.readContents(attRsrc1, attV1, logger); + ::test(!err, "Error reading template 1."); + + auto att1 = attRsrc1->findAttribute("Test1"); + ::test(!!att1, "Test1 attribute not found"); + auto att1StrItem = att1->findString("SubjectiveIQ"); + ::test(!!att1StrItem, "String item not found"); + auto att1IntItem = att1->findInt("ObjectiveIQ"); + ::test(!!att1IntItem, "Int item not found"); + + std::cout << "Resource 1 template type \"" << (attRsrc1->templateType()).data() << "\" " + << "v" << attRsrc1->templateVersion() << "\n" + << " Attribute 1 type \"" << att1->type() << "\" " + << "v" << att1->definition()->version() << "\n"; + + // Load a "new" version of the attribute (definitions only). + auto attRsrc2 = smtk::attribute::Resource::create(); + err = reader.readContents(attRsrc2, attV2, logger); + ::test(!err, "Error reading template 2."); + + // Create a default "Test1" attribute to hold the output of our item migration. + auto att2 = attRsrc2->createAttribute("Test"); + ::test(!!att2, "Test1 attribute not found"); + auto att2StrItem = att2->findString("SubjectiveIQ"); + ::test(!!att2StrItem, "String item not found"); + auto att2IntItem = att2->findInt("ObjectiveIQ"); + ::test(!!att2IntItem, "Int item not found"); + + // Now attempt to migrate items from att1 to att2. + std::cout << " Can we update \"" << att1->itemPath(att1StrItem) << "\"?\n"; + if ( + auto updater = updateManager->itemUpdaters(attRsrc1->templateType(), att1->type()) + .find( + att1->itemPath(att1StrItem), + att2StrItem->definition()->version(), + att1StrItem->definition()->version())) + { + std::cout << " Source item value: " << att1StrItem->value() << "\n"; + std::cout << " Previous item value: " << att2StrItem->value() << "\n"; + auto didUpdate = updater.Update(*att1StrItem, *att2StrItem, logger); + std::cout << " Updated item value: " << att2StrItem->value() << "\n"; + ::test(didUpdate, "Expected item to update."); + ::test(att2StrItem->value() == "Dumber", "Expected Dumb → Dumber."); + } + else + { + ::test(false, "Did not find updater for SubjectiveIQ."); + } + + std::cout << " Can we update \"" << att1->itemPath(att1IntItem) << "\"?\n"; + if ( + auto updater = updateManager->itemUpdaters(attRsrc1->templateType(), att1->type()) + .find( + att1->itemPath(att1IntItem), + att2IntItem->definition()->version(), + att1IntItem->definition()->version())) + { + std::cout << " Source item value: " << att1IntItem->value() << "\n"; + std::cout << " Previous item value: " << att2IntItem->value() << "\n"; + auto didUpdate = updater.Update(*att1IntItem, *att2IntItem, logger); + std::cout << " Updated item value: " << att2IntItem->value() << "\n"; + ::test(didUpdate, "Expected item to update."); + ::test(att2IntItem->value() == 42, "Expected 37 → 42."); + } + else + { + ::test(false, "Did not find updater for ObjectiveIQ."); + } + + return 0; +} diff --git a/smtk/attribute/testing/python/CMakeLists.txt b/smtk/attribute/testing/python/CMakeLists.txt index ca485365bc..967dab4405 100644 --- a/smtk/attribute/testing/python/CMakeLists.txt +++ b/smtk/attribute/testing/python/CMakeLists.txt @@ -39,6 +39,7 @@ set(smtkAttributePythonNewDataTests attributeQuery extensibleGroupTest groupItemRotateTest + itemUpdater referenceItemChildrenTest referenceItemIOTest valueItemRotateTest diff --git a/smtk/attribute/testing/python/itemUpdater.py b/smtk/attribute/testing/python/itemUpdater.py new file mode 100644 index 0000000000..2a17677c54 --- /dev/null +++ b/smtk/attribute/testing/python/itemUpdater.py @@ -0,0 +1,128 @@ +# ============================================================================= +# +# Copyright (c) Kitware, Inc. +# All rights reserved. +# See LICENSE.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even +# the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the above copyright notice for more information. +# +# ============================================================================= +import smtk.attribute +import smtk.common +import smtk.io + +# Version 1 of a simple template, plus an instance of it: +attXmlV1 = """ + + + + + + + + + + + + + 37 + Dumb + + + + +""" + +# Version 5 of a simple template. We want to upgrade the instance +# in attXmlV1 to this version. +attXmlV5 = """ + + + + + + + + + + + + + + + + +""" + + +def updateSubjectiveIQ(itemIn, itemOut, log): + """Update the "SubjectiveIQ" item by copying a modified version of its value.""" + if not itemIn or not itemOut: + return False + itemOut.setValue(itemIn.value() + 'er') + return True + + +def updateObjectiveIQ(itemIn, itemOut, log): + """Use the old "ObjectiveIQ" value as a baseline for the 2 values inside the group:""" + if not itemIn or not itemOut: + return False + itemOut.find(0, 'VerbalIQ').setValue(itemIn.value() + 5) + itemOut.find(0, 'LogicIQ').setValue(itemIn.value() - 5) + return True + + +if __name__ == '__main__': + # Create an attribute manager and register two item updaters: + updateManager = smtk.attribute.UpdateManager.create() + updateManager.registerItemUpdater( + "Example", "Test", "SubjectiveIQ", 1, 2, 3, updateSubjectiveIQ) + updateManager.registerItemUpdater( + "Example", "Test", "ObjectiveIQ", 1, 2, 3, updateObjectiveIQ) + + # Read in an old attribute resource and create a new one, + # then migrate items from the old resource's attribute to + # a matching attribute in the new resource. + logger = smtk.io.Logger.instance() + reader = smtk.io.AttributeReader() + rsrcv1 = smtk.attribute.Resource.create() + rsrcv5 = smtk.attribute.Resource.create() + if not reader.readContents(rsrcv1, attXmlV1, logger): + if not reader.readContents(rsrcv5, attXmlV5, logger): + att1 = rsrcv1.findAttribute('Test1') + siq1 = att1.findString('SubjectiveIQ') + oiq1 = att1.findInt('ObjectiveIQ') + att2 = rsrcv5.createAttribute('Test') + siq3 = att2.findString('SubjectiveIQ') + oiq3 = att2.findGroup('ObjectiveIQ') + updater = updateManager.findItemUpdater( + rsrcv1.templateType(), att1.type(), + att1.itemPath(siq1), + siq3.definition().version(), + siq1.definition().version()) + if not updater(siq1, siq3, logger) or \ + siq3.value() != 'Dumber': + raise ('Could not update SubjectiveIQ') + updater = updateManager.findItemUpdater( + rsrcv1.templateType(), att1.type(), + att1.itemPath(oiq1), + oiq3.definition().version(), + oiq1.definition().version()) + if not updater(oiq1, oiq3, logger): + raise ('Could not update ObjectiveIQ') + verbal = oiq3.find('VerbalIQ') + logic = oiq3.find('LogicIQ') + if verbal.value() != 42 or logic.value() != 32: + raise ('Improper update of ObjectiveIQ') + else: + print('Could not parse attXmlV5', logger.convertToString()) + else: + print('Could not parse attXmlV1', logger.convertToString()) + # For debugging: + # writer = smtk.io.AttributeWriter() + # writer.includeDefinitions(True) + # writer.includeInstances(True) + # xml = writer.writeContents(rsrcv5, logger) + # print(xml) diff --git a/smtk/attribute/update/AttributeUpdateFactory.h b/smtk/attribute/update/AttributeUpdateFactory.h new file mode 100644 index 0000000000..74a7576420 --- /dev/null +++ b/smtk/attribute/update/AttributeUpdateFactory.h @@ -0,0 +1,46 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= + +#ifndef smtk_attribute_update_AttributeUpdateFactory_h +#define smtk_attribute_update_AttributeUpdateFactory_h + +#include "smtk/CoreExports.h" +#include "smtk/SharedFromThis.h" +#include "smtk/common/update/Factory.h" +#include "smtk/io/Logger.h" + +namespace smtk +{ +namespace attribute +{ + +class Attribute; + +namespace update +{ + +/// The signature of functions used to migrate an item across schema. +using AttributeUpdater = std::function; + +/**\brief Update an item in an attribute resource. + * + */ +class SMTKCORE_EXPORT AttributeUpdateFactory + : public smtk::common::update::Factory +{ +public: + smtkTypeMacroBase(smtk::attribute::update::AttributeUpdater); +}; + +} // namespace update +} // namespace attribute +} // namespace smtk + +#endif // smtk_attribute_update_AttributeUpdateFactory_h diff --git a/smtk/attribute/update/ItemUpdateFactory.h b/smtk/attribute/update/ItemUpdateFactory.h new file mode 100644 index 0000000000..7622e3badf --- /dev/null +++ b/smtk/attribute/update/ItemUpdateFactory.h @@ -0,0 +1,45 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= + +#ifndef smtk_attribute_update_ItemUpdateFactory_h +#define smtk_attribute_update_ItemUpdateFactory_h + +#include "smtk/CoreExports.h" +#include "smtk/SharedFromThis.h" +#include "smtk/common/update/Factory.h" +#include "smtk/io/Logger.h" + +namespace smtk +{ +namespace attribute +{ + +class Item; + +namespace update +{ + +/// The signature of functions used to migrate an item across schema. +using ItemUpdater = std::function; + +/**\brief Update an item in an attribute resource. + * + */ +class SMTKCORE_EXPORT ItemUpdateFactory : public smtk::common::update::Factory +{ +public: + smtkTypeMacroBase(smtk::attribute::update::ItemUpdater); +}; + +} // namespace update +} // namespace attribute +} // namespace smtk + +#endif // smtk_attribute_update_ItemUpdateFactory_h diff --git a/smtk/attribute/update/ResourceUpdateFactory.h b/smtk/attribute/update/ResourceUpdateFactory.h new file mode 100644 index 0000000000..a1c688de61 --- /dev/null +++ b/smtk/attribute/update/ResourceUpdateFactory.h @@ -0,0 +1,45 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= + +#ifndef smtk_attribute_update_ResourceUpdateFactory_h +#define smtk_attribute_update_ResourceUpdateFactory_h + +#include "smtk/CoreExports.h" +#include "smtk/SharedFromThis.h" +#include "smtk/common/update/Factory.h" +#include "smtk/io/Logger.h" + +namespace smtk +{ +namespace attribute +{ + +class Resource; + +namespace update +{ + +/// The signature of functions used to migrate an item across schema. +using ResourceUpdater = std::function; + +/**\brief Update an item in an attribute resource. + * + */ +class SMTKCORE_EXPORT ResourceUpdateFactory : public smtk::common::update::Factory +{ +public: + smtkTypeMacroBase(smtk::attribute::update::ResourceUpdater); +}; + +} // namespace update +} // namespace attribute +} // namespace smtk + +#endif // smtk_attribute_update_ResourceUpdateFactory_h -- GitLab