--- /dev/null
+/*
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdio>
+#include <fstream>
+#include <sstream>
+#include <regex>
+#include <dirent.h>
+#include <PathUtil.h>
+#include "DatabaseManager.h"
+#include "SchemaLoader.h"
+
+/* Schema XML example:
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <ContextStore version="1">
+ <Schema uri="http://tizen.org/contextstore/name/subname" retention="24" limit="10">
+ <Privilege access="Read">http://tizen.org/privilege/healthinfo</Privilege>
+ <Privilege access="Write">http://tizen.org/privilege/internal/service</Privilege>
+ <Attribute name="name1" type="Integer"/>
+ <Attribute name="name2" type="Real"/>
+ <Attribute name="name3" type="Text"/>
+ </Schema>
+ </ContextStore>
+
+ */
+
+#define URI_REGEX(CATEGORY) R"~(^http:\/\/[\w-]+(\.[\w-]+)*\/)~" CATEGORY R"~(\/[\w-]+(\.[\w-]+)*(\/[\w-]+(\.[\w-]+)*)*$)~"
+#define COL_REGEX "^[A-Za-z]+\\w*$"
+
+#define CHK_NAME(NODE, NAME) (!xmlStrcmp(NODE->name, (const xmlChar*)(NAME)))
+
+using namespace ctx;
+
+static std::string __getXmlAttribute(xmlNode* node, const char* name)
+{
+ xmlChar* prop = xmlGetProp(node, reinterpret_cast<const xmlChar*>(name));
+ std::string out;
+ if (prop) {
+ out = reinterpret_cast<char*>(prop);
+ xmlFree(prop);
+ }
+ return out;
+}
+
+static std::string __getXmlContent(xmlNode* node)
+{
+ xmlChar* content = xmlNodeGetContent(node);
+ _D("%s", content);
+ std::string out;
+ if (content) {
+ out = reinterpret_cast<char*>(content);
+ xmlFree(content);
+ }
+ return out;
+}
+
+Schema::Schema() :
+ retention(0),
+ limit(0)
+{
+}
+
+bool Schema::valid()
+{
+ if (retention > MAX_RETENTION || limit > MAX_LIMIT)
+ return false;
+
+ if (retention == 0 && limit == 0)
+ return false;
+
+ if (attributes.empty())
+ return false;
+
+ std::regex uriRegex(URI_REGEX("contextstore"), std::regex::optimize);
+ if (!std::regex_match(uri, uriRegex))
+ return false;
+
+ std::regex privilegeRegex(URI_REGEX("privilege"), std::regex::optimize);
+ for (auto& privil : readPrivileges) {
+ if (!std::regex_match(privil, privilegeRegex))
+ return false;
+ }
+
+ for (auto& privil : writePrivileges) {
+ if (!std::regex_match(privil, privilegeRegex))
+ return false;
+ }
+
+ std::regex columnNameRegex(COL_REGEX, std::regex::optimize);
+ for (auto& attr: attributes) {
+ if (!std::regex_match(attr.first, columnNameRegex))
+ return false;
+ }
+
+ return true;
+}
+
+SchemaLoader::SchemaLoader()
+{
+}
+
+SchemaLoader::~SchemaLoader()
+{
+}
+
+bool SchemaLoader::load()
+{
+ bool result = true;
+ __getDatabase().beginTransaction();
+
+ if (!__createMetadataTables()) {
+ _E("Metadata table creation failed");
+ result = false;
+ }
+
+ try {
+ if (result && !__loadSchemaDir(__getSchemaDir())) {
+ _W("Schema loading failed");
+ result = false;
+ }
+ } catch (std::regex_error& e) {
+ _E("%s", e.what());
+ result = false;
+ }
+
+ __getDatabase().endTransaction();
+ return result;
+}
+
+bool SchemaLoader::__createMetadataTables()
+{
+ std::string path = PathUtil::getSystemPath(TZ_SYS_DATA, BASE_PATH "/init.sql");
+ IF_FAIL_RETURN(!path.empty(), false);
+
+ std::ifstream inFile(path);
+ std::stringstream buffer;
+ buffer << inFile.rdbuf();
+
+ return __getDatabase().execute(buffer.str(), NULL);
+}
+
+bool SchemaLoader::__loadSchemaDir(const std::string& dirPath)
+{
+ DIR* dir = NULL;
+ struct dirent entry;
+ struct dirent *result = NULL;
+
+ dir = opendir(dirPath.c_str());
+ IF_FAIL_RETURN_TAG(dir, false, _E, "Failed to open: %s", dirPath.c_str());
+
+ std::regex xmlFileRegex("^.*\\.(xml|XML)$", std::regex::optimize);
+
+ while (true) {
+ if (readdir_r(dir, &entry, &result) != 0)
+ continue;
+
+ if (result == NULL)
+ break;
+
+ std::string filename = entry.d_name;
+
+ if (!std::regex_match(filename, xmlFileRegex))
+ continue;
+
+ __parseSchemaFile(dirPath + "/" + filename);
+ }
+
+ closedir(dir);
+ return true;
+}
+
+void SchemaLoader::__parseSchemaFile(const std::string& filePath)
+{
+ _D("Parsing %s", filePath.c_str());
+
+ xmlDoc *doc = xmlReadFile(filePath.c_str(), NULL, 0);
+ IF_FAIL_VOID_TAG(doc, _W, "Could not parse %s", filePath.c_str());
+
+ xmlNode *node = xmlDocGetRootElement(doc);
+
+ if (!node || !CHK_NAME(node, "ContextStore")) {
+ _W("Not a ContextStore schema XML");
+ xmlFreeDoc(doc);
+ return;
+ }
+
+ for (node = node->children; node != NULL; node = node->next) {
+ if (node->type != XML_ELEMENT_NODE)
+ continue;
+
+ if (!CHK_NAME(node, "Schema")) {
+ _D("Skip '%s'", node->name);
+ continue;
+ }
+
+ Schema schema;
+ if (__parseSchema(node, schema)) {
+ __insertMetadata(schema);
+ __createStoreTable(schema);
+ }
+ }
+
+ xmlFreeDoc(doc);
+ return;
+}
+
+bool SchemaLoader::__parseSchema(xmlNode* node, Schema& schema)
+{
+ std::string retention = __getXmlAttribute(node, "retention");
+ std::string limit = __getXmlAttribute(node, "limit");
+ schema.uri = __getXmlAttribute(node, "uri");
+
+ for (node = node->children; node != NULL; node = node->next) {
+ if (node->type != XML_ELEMENT_NODE)
+ continue;
+
+ if (CHK_NAME(node, "Privilege"))
+ __parsePrivilege(node, schema);
+ else if (CHK_NAME(node, "Attribute"))
+ __parseAttribute(node, schema);
+ }
+
+ if (!retention.empty())
+ schema.retention = std::atoi(retention.c_str());
+
+ if (!limit.empty())
+ schema.limit = std::atoi(limit.c_str());
+
+ return schema.valid();
+}
+
+void SchemaLoader::__parsePrivilege(xmlNode* node, Schema& schema)
+{
+ std::string access = __getXmlAttribute(node, "access");
+ std::string privil = __getXmlContent(node);
+
+ if (access == "Read")
+ schema.readPrivileges.push_back(privil);
+ else if (access == "Write")
+ schema.writePrivileges.push_back(privil);
+}
+
+void SchemaLoader::__parseAttribute(xmlNode* node, Schema& schema)
+{
+ std::string name = __getXmlAttribute(node, "name");
+ std::string type = __getXmlAttribute(node, "type");
+
+ IF_FAIL_VOID(!name.empty());
+
+ if (type == "Integer")
+ schema.attributes.push_back(std::make_pair(name, Schema::AttributeType::INTEGER));
+ else if (type == "Real")
+ schema.attributes.push_back(std::make_pair(name, Schema::AttributeType::REAL));
+ else if (type == "Text")
+ schema.attributes.push_back(std::make_pair(name, Schema::AttributeType::TEXT));
+}
+
+void SchemaLoader::__insertMetadata(Schema& schema)
+{
+ char* updateSql = NULL;
+ char* insertSql = NULL;
+ int length = 0;
+
+ std::string readPrivil;
+ std::string writePrivil;
+
+ for (auto& item : schema.readPrivileges) {
+ readPrivil.append(item);
+ readPrivil.append(PRIVILEGE_DELIM);
+ }
+
+ for (auto& item : schema.writePrivileges) {
+ writePrivil.append(item);
+ writePrivil.append(PRIVILEGE_DELIM);
+ }
+
+ length = asprintf(&updateSql,
+ "UPDATE ContextStoreSchema"
+ " SET uri='%s', retention=%u, \"limit\"=%u, readPrivileges='%s', writePrivileges='%s'"
+ " WHERE uri='%s'",
+ schema.uri.c_str(), schema.retention, schema.limit, readPrivil.c_str(), writePrivil.c_str(),
+ schema.uri.c_str());
+ if (length <= 0) {
+ _E("Memory allocation failed");
+ return;
+ }
+
+ length = asprintf(&insertSql,
+ "INSERT OR IGNORE INTO ContextStoreSchema"
+ " (uri, retention, \"limit\", readPrivileges, writePrivileges)"
+ " VALUES ('%s', %u, %u, '%s', '%s')",
+ schema.uri.c_str(), schema.retention, schema.limit, readPrivil.c_str(), writePrivil.c_str());
+ if (length <= 0) {
+ g_free(updateSql);
+ _E("Memory allocation failed");
+ return;
+ }
+
+ __getDatabase().execute(updateSql, NULL);
+ __getDatabase().execute(insertSql, NULL);
+
+ g_free(updateSql);
+ g_free(insertSql);
+}
+
+void SchemaLoader::__createStoreTable(Schema& schema)
+{
+ std::string attr =
+ COL_TIMESTAMP " DATETIME DEFAULT CURRENT_TIMESTAMP,"
+ COL_OWNER_ID " TEXT NOT NULL DEFAULT ''";
+
+ for (auto& item : schema.attributes) {
+ attr = attr + "," + item.first;
+ switch (item.second) {
+ case Schema::AttributeType::INTEGER:
+ attr = attr + " INTEGER NOT NULL DEFAULT 0";
+ break;
+ case Schema::AttributeType::REAL:
+ attr = attr + " REAL NOT NULL DEFAULT 0";
+ break;
+ default:
+ attr = attr + " TEXT NOT NULL DEFAULT ''";
+ break;
+ }
+ }
+
+ std::string query =
+ "CREATE TABLE IF NOT EXISTS [" + schema.uri + "] (" + attr + ")";
+
+ __getDatabase().execute(query.c_str(), NULL);
+}
+
+
+SystemSchemaLoader::SystemSchemaLoader()
+{
+}
+
+Database& SystemSchemaLoader::__getDatabase()
+{
+ return DatabaseManager::getSystem();
+}
+
+std::string SystemSchemaLoader::__getSchemaDir()
+{
+ return PathUtil::getSystemPath(TZ_SYS_DATA, BASE_PATH "/system");
+}
+
+
+UserSchemaLoader::UserSchemaLoader()
+{
+}
+
+Database& UserSchemaLoader::__getDatabase()
+{
+ return DatabaseManager::getUser();
+}
+
+std::string UserSchemaLoader::__getSchemaDir()
+{
+ return PathUtil::getSystemPath(TZ_SYS_DATA, BASE_PATH "/user");
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __CONTEXT_STORE_SCHEMA_LOADER_H__
+#define __CONTEXT_STORE_SCHEMA_LOADER_H__
+
+#include <utility>
+#include <string>
+#include <vector>
+#include <libxml/tree.h>
+#include <Database.h>
+#include <ContextStoreTypesPrivate.h>
+
+namespace ctx {
+
+ class Schema {
+ public:
+ enum class AttributeType {
+ UNDEFINED = 0,
+ INTEGER = 1,
+ REAL = 2,
+ TEXT = 3
+ };
+
+ Schema();
+ bool valid();
+
+ std::string uri;
+ unsigned int retention;
+ unsigned int limit;
+ std::vector<std::string> readPrivileges;
+ std::vector<std::string> writePrivileges;
+ std::vector<std::pair<std::string, AttributeType>> attributes;
+ };
+
+ class SchemaLoader {
+ public:
+ virtual ~SchemaLoader();
+
+ bool load();
+
+ private:
+ bool __createMetadataTables();
+ bool __loadSchemaDir(const std::string& dirPath);
+ void __parseSchemaFile(const std::string& filePath);
+ bool __parseSchema(xmlNode* node, Schema& schema);
+ void __parsePrivilege(xmlNode* node, Schema& schema);
+ void __parseAttribute(xmlNode* node, Schema& schema);
+ void __insertMetadata(Schema& schema);
+ void __createStoreTable(Schema& schema);
+
+ virtual std::string __getSchemaDir() = 0;
+ virtual Database& __getDatabase() = 0;
+
+ protected:
+ SchemaLoader();
+ };
+
+ class SystemSchemaLoader : public SchemaLoader {
+ public:
+ SystemSchemaLoader();
+
+ private:
+ std::string __getSchemaDir();
+ Database& __getDatabase();
+ };
+
+ class UserSchemaLoader : public SchemaLoader {
+ public:
+ UserSchemaLoader();
+
+ private:
+ std::string __getSchemaDir();
+ Database& __getDatabase();
+ };
+}
+
+#endif /* __CONTEXT_STORE_SCHEMA_LOADER_H__ */