diff --git a/demo/agent/agent.cfg b/demo/agent/agent.cfg index e9d3f23d1..a407915d9 100644 --- a/demo/agent/agent.cfg +++ b/demo/agent/agent.cfg @@ -1,5 +1,5 @@ Devices = Devices.xml -SchemaVersion = 2.0 +SchemaVersion = 2.2 WorkerThreads = 3 MonitorConfigFiles = yes Port = 5001 diff --git a/src/mtconnect/configuration/agent_config.cpp b/src/mtconnect/configuration/agent_config.cpp index eb2aad876..d5166da2c 100644 --- a/src/mtconnect/configuration/agent_config.cpp +++ b/src/mtconnect/configuration/agent_config.cpp @@ -964,7 +964,7 @@ namespace mtconnect::configuration { { adapterOptions[configuration::Device] = *device->getUuid(); } - + if (!device) { LOG(warning) << "Cannot locate device name '" << deviceName << "', assuming dynamic"; diff --git a/src/mtconnect/sink/rest_sink/cached_file.hpp b/src/mtconnect/sink/rest_sink/cached_file.hpp index 417c16e85..da2ca52f5 100644 --- a/src/mtconnect/sink/rest_sink/cached_file.hpp +++ b/src/mtconnect/sink/rest_sink/cached_file.hpp @@ -94,8 +94,14 @@ namespace mtconnect::sink::rest_sink { if (cached) { allocate(m_size); - auto file = std::fopen(path.string().c_str(), "rb"); - m_size = std::fread(m_buffer, 1, m_size, file); + std::filebuf file; + if (file.open(m_path, std::ios::binary | std::ios::in) != nullptr) + m_size = file.sgetn(m_buffer, m_size); + else + { + LOG(warning) << "Cannot open cached file: " << path; + m_cached = false; + } } m_lastWrite = std::filesystem::last_write_time(m_path); } diff --git a/src/mtconnect/sink/rest_sink/rest_service.cpp b/src/mtconnect/sink/rest_sink/rest_service.cpp index 7fd3aa0ea..34a548f9a 100644 --- a/src/mtconnect/sink/rest_sink/rest_service.cpp +++ b/src/mtconnect/sink/rest_sink/rest_service.cpp @@ -17,6 +17,8 @@ #include "rest_service.hpp" +#include + #include "mtconnect/configuration/config_options.hpp" #include "mtconnect/entity/xml_parser.hpp" #include "mtconnect/pipeline/shdr_token_mapper.hpp" @@ -270,6 +272,7 @@ namespace mtconnect { void RestService::loadStyle(const ptree &tree, const char *styleName, XmlPrinter *xmlPrinter, StyleFunction styleFunction) { + namespace fs = std::filesystem; auto style = tree.get_child_optional(styleName); if (style) { @@ -281,10 +284,53 @@ namespace mtconnect { else { (xmlPrinter->*styleFunction)(*location); - auto path = style->get_optional("Path"); - if (path) + auto configPath = style->get_optional("Path"); + if (configPath) + { + m_fileCache.registerFile(*location, *configPath, m_schemaVersion); + } + + if (auto fc = m_fileCache.getFile(*location)) + { + try + { + unique_ptr buffer(new char[fc->m_size]); + std::filebuf file; + if (file.open(fc->m_path, std::ios::binary | std::ios::in) == nullptr) + throw std::runtime_error("Cannot open file for reading"); + + auto len = file.sgetn(buffer.get(), fc->m_size); + file.close(); + if (len <= 0) + throw std::runtime_error("Cannot read from file"); + + string_view sv(buffer.get(), len); + + std::ofstream out(fc->m_path, std::ios::binary | std::ios_base::out); + if (!out.is_open()) + throw std::runtime_error("Cannot open file for writing"); + + std::ostream_iterator oi(out); + + std::regex reg( + "(xmlns:[A-Za-z]+=\"urn:mtconnect.org:MTConnect[^:]+:)" + "[[:digit:]]+\\.[[:digit:]]+(\")"); + std::regex_replace( + oi, sv.begin(), sv.end(), reg, "$01" + m_schemaVersion + "$2", + std::regex_constants::match_default | std::regex_constants::match_any); + } + catch (std::runtime_error ec) + { + LOG(error) << "Cannot update sylesheet: " << ec.what() << " (" << fc->m_path << ')'; + } + catch (...) + { + LOG(error) << "Cannot update sylesheet: (" << fc->m_path << ')'; + } + } + else { - m_fileCache.registerFile(*location, *path, m_schemaVersion); + LOG(warning) << "Cannot find path for style file: " << *location; } } } diff --git a/test_package/agent_test.cpp b/test_package/agent_test.cpp index 45f9ee5e4..2615536cd 100644 --- a/test_package/agent_test.cpp +++ b/test_package/agent_test.cpp @@ -3013,8 +3013,9 @@ TEST_F(AgentTest, should_not_add_spaces_to_output) ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", ""); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", ""); } - - m_agentTestHelper->m_adapter->processData("2024-01-22T20:00:00Z|program| |block| "); + + m_agentTestHelper->m_adapter->processData( + "2024-01-22T20:00:00Z|program| |block| "); { PARSE_XML_RESPONSE("/current"); diff --git a/test_package/config_test.cpp b/test_package/config_test.cpp index 1a61c0449..2eeba2758 100644 --- a/test_package/config_test.cpp +++ b/test_package/config_test.cpp @@ -91,9 +91,15 @@ namespace { return root; } - fs::path copyFile(const std::string &src, fs::path target, chrono::seconds delta) + fs::path copySampleFile(const std::string &src, fs::path target, chrono::seconds delta) { - fs::path file {fs::path(TEST_RESOURCE_DIR) / "samples" / src}; + fs::path file {fs::path("samples") / src}; + return copyFile(file, target, delta); + } + + fs::path copyFile(const fs::path src, fs::path target, chrono::seconds delta) + { + fs::path file {fs::path(TEST_RESOURCE_DIR) / src}; fs::copy_file(file, target, fs::copy_options::overwrite_existing); auto t = fs::last_write_time(target); @@ -979,7 +985,7 @@ Port = 0 cfg << "Devices = " << devices << endl; } - copyFile("min_config.xml", devices, 60min); + copySampleFile("min_config.xml", devices, 60min); boost::program_options::variables_map options; boost::program_options::variable_value value(boost::optional(config.string()), false); @@ -1058,7 +1064,7 @@ Port = 0 cfg << "Devices = " << devices << endl; } - copyFile("min_config.xml", devices, 1min); + copySampleFile("min_config.xml", devices, 1min); boost::program_options::variables_map options; boost::program_options::variable_value value(boost::optional(config.string()), false); @@ -1125,7 +1131,7 @@ Port = 0 cfg << "Devices = " << devices << endl; } - copyFile("min_config.xml", devices, 0s); + copySampleFile("min_config.xml", devices, 0s); boost::program_options::variables_map options; boost::program_options::variable_value value(boost::optional(config.string()), false); @@ -1199,7 +1205,7 @@ Port = 0 cfg << "Devices = " << devices << endl; } - copyFile("min_config.xml", devices, 1min); + copySampleFile("min_config.xml", devices, 1min); boost::program_options::variables_map options; boost::program_options::variable_value value(boost::optional(config.string()), false); @@ -1316,7 +1322,7 @@ Port = 0 cfg << "Devices = " << devices << endl; } - copyFile("min_config.xml", devices, 10min); + copySampleFile("min_config.xml", devices, 10min); replaceTextInFile(devices, "2.0", "1.2"); boost::program_options::variables_map options; @@ -1415,7 +1421,7 @@ Adapters { cfg << "Devices = " << devices << endl; } - copyFile("empty.xml", devices, 0min); + copySampleFile("empty.xml", devices, 0min); boost::program_options::variables_map options; boost::program_options::variable_value value(boost::optional(config.string()), false); @@ -1527,7 +1533,7 @@ Port = 0 cfg << "Devices = " << devices << endl; } - copyFile("dyn_load.xml", devices, 0min); + copySampleFile("dyn_load.xml", devices, 0min); boost::program_options::variables_map options; boost::program_options::variable_value value(boost::optional(config.string()), false); @@ -1656,7 +1662,7 @@ Port = 0 cfg << "Devices = " << devices << endl; } - copyFile("dyn_load.xml", devices, 0min); + copySampleFile("dyn_load.xml", devices, 0min); boost::program_options::variables_map options; boost::program_options::variable_value value(boost::optional(config.string()), false); @@ -1718,7 +1724,7 @@ Port = 0 cfg << "Devices = " << devices << endl; } - copyFile("dyn_load.xml", devices, 0min); + copySampleFile("dyn_load.xml", devices, 0min); boost::program_options::variables_map options; boost::program_options::variable_value value(boost::optional(config.string()), false); @@ -1836,7 +1842,7 @@ Adapters { cfg << "Devices = " << devices << endl; } - copyFile("empty.xml", devices, 0min); + copySampleFile("empty.xml", devices, 0min); boost::program_options::variables_map options; boost::program_options::variable_value value(boost::optional(config.string()), false); @@ -1941,7 +1947,7 @@ Port = 0 cfg << "Devices = " << devices << endl; } - copyFile("dyn_load.xml", devices, 0min); + copySampleFile("dyn_load.xml", devices, 0min); boost::program_options::variables_map options; boost::program_options::variable_value value(boost::optional(config.string()), false); @@ -2068,7 +2074,7 @@ Adapters { cfg << "Devices = " << devices << endl; } - copyFile("empty.xml", devices, 0min); + copySampleFile("empty.xml", devices, 0min); boost::program_options::variables_map options; boost::program_options::variable_value value(boost::optional(config.string()), false); @@ -2190,7 +2196,7 @@ ServiceName="some_prefix_${CONFIG_TEST}_suffix" TEST_F(ConfigTest, should_find_device_file_in_config_path) { fs::path root {createTempDirectory("13")}; - copyFile("empty.xml", root / "test.xml", 0min); + copySampleFile("empty.xml", root / "test.xml", 0min); chdir(m_cwd.string().c_str()); m_config->updateWorkingDirectory(); @@ -2257,7 +2263,7 @@ AgentDeviceUUID = SOME_UUID const auto &ad = m_config->getAgent()->getAgentDevice(); ASSERT_EQ("SOME_UUID", *(ad->getUuid())); } - + TEST_F(ConfigTest, should_set_device_uuid_when_specified_in_adapter_config) { string config(R"DOC( @@ -2277,7 +2283,7 @@ Adapters { ASSERT_TRUE(dev); ASSERT_EQ("NEW-UUID", *(dev->getUuid())); } - + TEST_F(ConfigTest, should_set_default_device_uuid_when_specified_in_adapter_config) { string config(R"DOC( @@ -2298,4 +2304,135 @@ Adapters { ASSERT_EQ("NEW-UUID", *(dev->getUuid())); } + TEST_F(ConfigTest, should_update_stylesheet_versions) + { + fs::path root {createTempDirectory("14")}; + + fs::path styleDir {root / "styles"}; + fs::create_directory(styleDir); + + fs::path styles {styleDir / "styles.xsl"}; + copyFile("styles/styles.xsl", styles, 0min); + + fs::path devices(root / "Devices.xml"); + copySampleFile("empty.xml", devices, 0min); + + fs::path config {root / "agent.cfg"}; + { + ofstream cfg(config.string()); + cfg << R"DOC( +SchemaVersion = 2.2 +)DOC"; + cfg << "Devices = " << devices << endl; + cfg << R"DOC( +Files { + styles { + Path = ./styles + Location = /styles/ + } +} +DevicesStyle { Location = /styles/styles.xsl } +)DOC"; + } + + boost::program_options::variables_map options; + boost::program_options::variable_value value(boost::optional(config.string()), false); + options.insert(make_pair("config-file"s, value)); + + m_config->initialize(options); + + ifstream file(styles); + ASSERT_TRUE(file.is_open()); + + stringstream sf; + sf << file.rdbuf(); + + ASSERT_EQ(R"DOC( + + + + + + + + +)DOC", + sf.str()); + + m_config->stop(); + } + + TEST_F(ConfigTest, should_update_stylesheet_versions_with_path) + { + fs::path root {createTempDirectory("15")}; + + fs::path styleDir {root / "styles"}; + fs::create_directory(styleDir); + + fs::path styles {styleDir / "styles.xsl"}; + copyFile("styles/styles.xsl", styles, 0min); + + fs::path devices(root / "Devices.xml"); + copySampleFile("empty.xml", devices, 0min); + + fs::path config {root / "agent.cfg"}; + { + ofstream cfg(config.string()); + cfg << R"DOC( +SchemaVersion = 2.2 +)DOC"; + cfg << "Devices = " << devices << endl; + cfg << R"DOC( +DevicesStyle { + Location = /styles/styles.xsl + Path = ./styles/styles.xsl +} +)DOC"; + } + + boost::program_options::variables_map options; + boost::program_options::variable_value value(boost::optional(config.string()), false); + options.insert(make_pair("config-file"s, value)); + + m_config->initialize(options); + + ifstream file(styles); + ASSERT_TRUE(file.is_open()); + + stringstream sf; + sf << file.rdbuf(); + + ASSERT_EQ(R"DOC( + + + + + + + + +)DOC", + sf.str()); + + m_config->stop(); + } + } // namespace diff --git a/test_package/resources/styles/styles.xsl b/test_package/resources/styles/styles.xsl new file mode 100644 index 000000000..7270840e5 --- /dev/null +++ b/test_package/resources/styles/styles.xsl @@ -0,0 +1,18 @@ + + + + + + + + +