catalog: implement catalog driver and facade

This commit also refactories the QueryAdapter's code, adds corresponding
unit-test. Catalog-adapter and catalog do not use template, so the definition
are moved to corresponding cpp files.

refs: #2599, #2600

Change-Id: I2be492ec3c2538e865bfa7c09ac8cd49e2a9527d
diff --git a/catalog/tests/unit-test-time-fixture.hpp b/catalog/tests/unit-test-time-fixture.hpp
new file mode 100644
index 0000000..852b5a3
--- /dev/null
+++ b/catalog/tests/unit-test-time-fixture.hpp
@@ -0,0 +1,68 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2014 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef ATMOS_TESTS_UNIT_TESTS_UNIT_TEST_TIME_FIXTURE_HPP
+#define ATMOS_TESTS_UNIT_TESTS_UNIT_TEST_TIME_FIXTURE_HPP
+
+#include <ndn-cxx/util/time-unit-test-clock.hpp>
+#include <boost/asio.hpp>
+
+namespace atmos {
+namespace tests {
+
+class UnitTestTimeFixture
+{
+public:
+  UnitTestTimeFixture()
+    : steadyClock(std::make_shared<ndn::time::UnitTestSteadyClock>())
+    , systemClock(std::make_shared<ndn::time::UnitTestSystemClock>())
+  {
+    ndn::time::setCustomClocks(steadyClock, systemClock);
+  }
+
+  ~UnitTestTimeFixture()
+  {
+    ndn::time::setCustomClocks(nullptr, nullptr);
+  }
+
+  void
+  advanceClocks(const ndn::time::nanoseconds& tick, size_t nTicks = 1)
+  {
+    for (size_t i = 0; i < nTicks; ++i) {
+      steadyClock->advance(tick);
+      systemClock->advance(tick);
+
+      if (io.stopped())
+        io.reset();
+      io.poll();
+    }
+  }
+
+public:
+  std::shared_ptr<ndn::time::UnitTestSteadyClock> steadyClock;
+  std::shared_ptr<ndn::time::UnitTestSystemClock> systemClock;
+  boost::asio::io_service io;
+};
+
+} // namespace tests
+} // namespace atmos
+
+#endif // ATMOS_TESTS_UNIT_TESTS_UNIT_TEST_TIME_FIXTURE_HPP
diff --git a/catalog/tests/unit-tests/query/test-query-adapter.cpp b/catalog/tests/unit-tests/query/test-query-adapter.cpp
new file mode 100644
index 0000000..1e8d0c1
--- /dev/null
+++ b/catalog/tests/unit-tests/query/test-query-adapter.cpp
@@ -0,0 +1,414 @@
+/** NDN-Atmos: Cataloging Service for distributed data originally developed
+ *  for atmospheric science data
+ *  Copyright (C) 2015 Colorado State University
+ *
+ *  NDN-Atmos is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  NDN-Atmos is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with NDN-Atmos.  If not, see <http://www.gnu.org/licenses/>.
+**/
+
+#include "query/query-adapter.hpp"
+#include "boost-test.hpp"
+#include "../../unit-test-time-fixture.hpp"
+#include "util/config-file.hpp"
+
+#include <boost/mpl/list.hpp>
+#include <boost/thread.hpp>
+#include <ndn-cxx/util/dummy-client-face.hpp>
+#include <boost/property_tree/info_parser.hpp>
+
+namespace atmos{
+namespace tests{
+  using ndn::util::DummyClientFace;
+  using ndn::util::makeDummyClientFace;
+
+  class QueryAdapterTest : public query::QueryAdapter<std::string>
+  {
+  public:
+    QueryAdapterTest(const std::shared_ptr<ndn::util::DummyClientFace>& face,
+                     const std::shared_ptr<ndn::KeyChain>& keyChain)
+      : query::QueryAdapter<std::string>(face, keyChain)
+    {
+    }
+
+    virtual
+    ~QueryAdapterTest()
+    {
+    }
+
+    void setPrefix(const ndn::Name& prefix)
+    {
+      m_prefix = prefix;
+    }
+
+    void setSigningId(const ndn::Name& signingId)
+    {
+      m_signingId = signingId;
+    }
+
+    const ndn::Name
+    getPrefix()
+    {
+      return m_prefix;
+    }
+
+    const ndn::Name
+    getSigningId()
+    {
+      return m_signingId;
+    }
+
+    std::shared_ptr<ndn::Data>
+    getAckData(std::shared_ptr<const ndn::Interest> interest, const ndn::Name::Component& version)
+    {
+      return makeAckData(interest, version);
+    }
+
+    void
+    parseJsonTest(std::string& targetSql,
+                  Json::Value& parsedFromString,
+                  bool& autocomplete)
+    {
+      std::stringstream resultSql;
+      json2Sql(resultSql, parsedFromString, autocomplete);
+      targetSql.assign(resultSql.str());
+    }
+
+    std::shared_ptr<ndn::Data>
+    getReplyData(const ndn::Name& segmentPrefix,
+                 const Json::Value& value,
+                 uint64_t segmentNo,
+                 bool isFinalBlock,
+                 bool isAutocomplete)
+    {
+      return makeReplyData(segmentPrefix, value, segmentNo, isFinalBlock, isAutocomplete);
+    }
+
+    void
+    queryTest(std::shared_ptr<const ndn::Interest> interest)
+    {
+      runJsonQuery(interest);
+    }
+
+    void
+    prepareSegments(const ndn::Name& segmentPrefix,
+                    const std::string& sqlString,
+                    bool autocomplete)
+    {
+      BOOST_CHECK_EQUAL(sqlString, "SELECT name FROM cmip5 WHERE name=\'test\';");
+      Json::Value fileList;
+      fileList.append("/ndn/test1");
+      fileList.append("/ndn/test2");
+      fileList.append("/ndn/test3");
+
+      std::shared_ptr<ndn::Data> data = makeReplyData(segmentPrefix,
+                                                      fileList,
+                                                      0,
+                                                      true,
+                                                      false);
+      m_mutex.lock();
+      m_cache.insert(*data);
+      m_mutex.unlock();
+    }
+
+    std::shared_ptr<const ndn::Data>
+    getDataFromActiveQuery(const std::string& jsonQuery)
+    {
+      m_mutex.lock();
+      if (m_activeQueryToFirstResponse.find(jsonQuery) != m_activeQueryToFirstResponse.end()) {
+        auto iter = m_activeQueryToFirstResponse.find(jsonQuery);
+        if (iter != m_activeQueryToFirstResponse.end()) {
+          m_mutex.unlock();
+          return iter->second;
+        }
+      }
+      m_mutex.unlock();
+      return std::shared_ptr<const ndn::Data>();
+    }
+
+    std::shared_ptr<const ndn::Data>
+    getDataFromCache(const ndn::Interest& interest)
+    {
+      return m_cache.find(interest);
+    }
+
+    void
+    configAdapter(const util::ConfigSection& section,
+                  const ndn::Name& prefix)
+    {
+      onConfig(section, false, std::string("test.txt"), prefix);
+    }
+  };
+
+  class QueryAdapterFixture : public UnitTestTimeFixture
+  {
+  public:
+    QueryAdapterFixture()
+      : face(makeDummyClientFace(io))
+      , keyChain(new ndn::KeyChain())
+      , queryAdapterTest1(face, keyChain)
+      , queryAdapterTest2(face, keyChain)
+    {
+    }
+
+    virtual
+    ~QueryAdapterFixture()
+    {
+    }
+
+  protected:
+    void
+    initializeQueryAdapterTest1()
+    {
+      util::ConfigSection section;
+      try {
+        std::stringstream ss;
+        ss << "signingId /test/signingId\
+             database                   \
+             {                          \
+              dbServer localhost        \
+              dbName testdb             \
+              dbUser testuser           \
+              dbPasswd testpwd          \
+             }";
+        boost::property_tree::read_info(ss, section);
+      }
+      catch (boost::property_tree::info_parser_error &e) {
+        std::cout << "Failed to read config file " << e.what() << std::endl;;
+      }
+      queryAdapterTest1.configAdapter(section, ndn::Name("/test"));
+    }
+
+    void
+    initializeQueryAdapterTest2()
+    {
+      util::ConfigSection section;
+      try {
+        std::stringstream ss;
+        ss << "database\
+             {                                  \
+              dbServer localhost                \
+              dbName testdb                     \
+              dbUser testuser                   \
+              dbPasswd testpwd                  \
+             }";
+        boost::property_tree::read_info(ss, section);
+      }
+      catch (boost::property_tree::info_parser_error &e) {
+        std::cout << "Failed to read config file " << e.what() << std::endl;;
+      }
+      queryAdapterTest2.configAdapter(section, ndn::Name("/test"));
+    }
+
+  protected:
+    std::shared_ptr<DummyClientFace> face;
+    std::shared_ptr<ndn::KeyChain> keyChain;
+    QueryAdapterTest queryAdapterTest1;
+    QueryAdapterTest queryAdapterTest2;
+  };
+
+  BOOST_FIXTURE_TEST_SUITE(QueryAdapterTestSuite, QueryAdapterFixture)
+
+  BOOST_AUTO_TEST_CASE(BasicQueryAdapterTest1)
+  {
+    BOOST_CHECK(queryAdapterTest1.getPrefix() == ndn::Name());
+    BOOST_CHECK(queryAdapterTest1.getSigningId() == ndn::Name());
+  }
+
+  BOOST_AUTO_TEST_CASE(BasicQueryAdapterTest2)
+  {
+    initializeQueryAdapterTest1();
+    BOOST_CHECK(queryAdapterTest1.getPrefix() == ndn::Name("/test"));
+    BOOST_CHECK(queryAdapterTest1.getSigningId() == ndn::Name("/test/signingId"));
+  }
+
+  BOOST_AUTO_TEST_CASE(QueryAdapterJsonParseNormalTest)
+  {
+    Json::Value testJson;
+    testJson["name"] = "test";
+    testJson["activity"] = "testActivity";
+    testJson["product"] = "testProduct";
+
+    std::string dstString;
+    bool autocomplete = false;
+    queryAdapterTest1.parseJsonTest(dstString, testJson, autocomplete);
+    BOOST_CHECK_EQUAL(dstString, "SELECT name FROM cmip5 WHERE\
+ activity=\'testActivity\' AND name='test\' AND product=\'testProduct\';");
+    BOOST_CHECK_EQUAL(autocomplete, false);
+  }
+
+  BOOST_AUTO_TEST_CASE(QueryAdapterJsonParseEmptyTest)
+  {
+    Json::Value testJson;
+
+    std::string dstString;
+    bool autocomplete = false;
+    queryAdapterTest1.parseJsonTest(dstString, testJson, autocomplete);
+    BOOST_CHECK_EQUAL(dstString, "SELECT name FROM cmip5 limit 0;");
+    BOOST_CHECK_EQUAL(autocomplete, false);
+  }
+
+  BOOST_AUTO_TEST_CASE(QueryAdapterJsonParseAllItemsTest)
+  {
+    Json::Value testJson;
+    testJson["name"] = "test";
+    testJson["activity"] = "testActivity";
+    testJson["product"] = "testProduct";
+    testJson["origanization"] = "testOrg";
+    testJson["model"] = "testModel";
+    testJson["experiment"] = "testExperiment";
+    testJson["frequency"] = "testFrenquency";
+    testJson["modeling realm"] = "testModeling";
+    testJson["variable name"] = "testVarName";
+    testJson["ensemble member"] = "testEnsembleMember";
+    testJson["ensemble"] = "testEnsemble";
+    testJson["sample granularity"] = "testSampleGranularity";
+    testJson["start time"] = "testStartTime";
+    testJson["field campaign"] = "testFieldCampaign";
+    testJson["optical properties for radiation"] = "testOptProperties";
+    testJson["grid resolution"] = "testGridResolution";
+    testJson["output type"] = "testOutputType";
+    testJson["timestamp"] = "testTimestamp";
+
+    std::string dstString;
+    bool autocomplete = false;
+    queryAdapterTest1.parseJsonTest(dstString, testJson, autocomplete);
+    BOOST_CHECK_EQUAL(dstString, "SELECT name FROM cmip5 WHERE activity=\'testActivity\' AND \
+ensemble=\'testEnsemble\' AND ensemble member=\'testEnsembleMember\' AND \
+experiment=\'testExperiment\' AND field campaign=\'testFieldCampaign\' AND \
+frequency=\'testFrenquency\' AND grid resolution=\'testGridResolution\' AND \
+model=\'testModel\' AND modeling realm=\'testModeling\' AND name=\'test\' AND \
+optical properties for radiation=\'testOptProperties\' AND origanization=\'testOrg\' AND \
+output type=\'testOutputType\' AND product=\'testProduct\' AND sample \
+granularity=\'testSampleGranularity\' AND start time=\'testStartTime\' AND \
+timestamp=\'testTimestamp\' AND variable name=\'testVarName\';");
+    BOOST_CHECK_EQUAL(autocomplete, false);
+  }
+
+  BOOST_AUTO_TEST_CASE(QueryAdapterJsonParseSearchTest)
+  {
+    Json::Value testJson;
+    testJson["name"] = "test";
+    testJson["?"] = "serchTest";
+
+    std::string dstString;
+    bool autocomplete = false;
+    queryAdapterTest1.parseJsonTest(dstString, testJson, autocomplete);
+    BOOST_CHECK_EQUAL(dstString,
+      "SELECT name FROM cmip5 WHERE name REGEXP \'^serchTest\' AND name=\'test\';");
+    BOOST_CHECK_EQUAL(autocomplete, true);
+  }
+
+  BOOST_AUTO_TEST_CASE(QueryAdapterMakeAckDataTest)
+  {
+    ndn::Interest interest(ndn::Name("/test/ack/data/json"));
+    interest.setInterestLifetime(ndn::time::milliseconds(1000));
+    interest.setMustBeFresh(true);
+    std::shared_ptr<const ndn::Interest> interestPtr = std::make_shared<ndn::Interest>(interest);
+
+    const ndn::name::Component version
+      = ndn::name::Component::fromVersion(1);
+
+    std::shared_ptr<ndn::Data> data = queryAdapterTest2.getAckData(interestPtr, version);
+    BOOST_CHECK_EQUAL(data->getName().toUri(), "/test/ack/data/json/%FD%01/OK");
+    BOOST_CHECK_EQUAL(data->getContent().value_size(), 0);
+  }
+
+  BOOST_AUTO_TEST_CASE(QueryAdapterMakeReplyDataTest1)
+  {
+    Json::Value fileList;
+    fileList.append("/ndn/test1");
+    fileList.append("/ndn/test2");
+
+    const ndn::Name prefix("/atmos/test/prefix");
+
+    std::shared_ptr<ndn::Data> data = queryAdapterTest2.getReplyData(prefix,
+      fileList,
+      1,
+      false,
+      false);
+    BOOST_CHECK_EQUAL(data->getName().toUri(), "/atmos/test/prefix/%00%01");
+    BOOST_CHECK_EQUAL(data->getFinalBlockId(), ndn::Name::Component(""));
+    const std::string jsonRes(reinterpret_cast<const char*>(data->getContent().value()));
+    Json::Value parsedFromString;
+    Json::Reader reader;
+    BOOST_CHECK_EQUAL(reader.parse(jsonRes, parsedFromString), true);
+    BOOST_CHECK_EQUAL(parsedFromString["results"].size(), 2);
+    BOOST_CHECK_EQUAL(parsedFromString["results"][0], "/ndn/test1");
+    BOOST_CHECK_EQUAL(parsedFromString["results"][1], "/ndn/test2");
+  }
+
+  BOOST_AUTO_TEST_CASE(QueryAdapterMakeReplyDataTest2)
+  {
+    Json::Value fileList;
+    fileList.append("/ndn/test1");
+    const ndn::Name prefix("/atmos/test/prefix");
+
+    std::shared_ptr<ndn::Data> data = queryAdapterTest2.getReplyData(prefix,
+      fileList,
+      2,
+      true,
+      true);
+    // the finalBlock does not work for jsNDN
+    BOOST_CHECK_EQUAL(data->getName().toUri(), "/atmos/test/prefix/%00%02");
+    BOOST_CHECK_EQUAL(data->getFinalBlockId(), ndn::Name::Component::fromSegment(2));
+    const std::string jsonRes(reinterpret_cast<const char*>(data->getContent().value()));
+    Json::Value parsedFromString;
+    Json::Reader reader;
+    BOOST_CHECK_EQUAL(reader.parse(jsonRes, parsedFromString), true);
+    BOOST_CHECK_EQUAL(parsedFromString["next"].size(), 1);
+    BOOST_CHECK_EQUAL(parsedFromString["next"][0], "/ndn/test1");
+  }
+
+  BOOST_AUTO_TEST_CASE(QueryAdapterQueryProcessTest)
+  {
+    initializeQueryAdapterTest2();
+    Json::Value query;
+    query["name"] = "test";
+    Json::FastWriter fastWriter;
+    std::string jsonMessage = fastWriter.write(query);
+    jsonMessage.erase(std::remove(jsonMessage.begin(), jsonMessage.end(), '\n'), jsonMessage.end());
+    std::shared_ptr<ndn::Interest> queryInterest
+      = std::make_shared<ndn::Interest>(ndn::Name("/test/query").append(jsonMessage.c_str()));
+
+    queryAdapterTest2.queryTest(queryInterest);
+    auto ackData = queryAdapterTest2.getDataFromActiveQuery(jsonMessage);
+
+    BOOST_CHECK(ackData);
+    if (ackData) {
+      BOOST_CHECK_EQUAL(ackData->getName().getPrefix(3),
+      ndn::Name("/test/query/%7B%22name%22%3A%22test%22%7D"));
+      BOOST_CHECK_EQUAL(ackData->getName().getSubName(4, 1), ndn::Name("OK"));
+      BOOST_CHECK_EQUAL(ackData->getContent().value_size(), 0);
+    }
+
+    std::shared_ptr<ndn::Interest> resultInterest
+      = std::make_shared<ndn::Interest>(ndn::Name("/test/query-results"));
+    auto replyData = queryAdapterTest2.getDataFromCache(*resultInterest);
+    BOOST_CHECK(replyData);
+    if (replyData){
+      BOOST_CHECK_EQUAL(replyData->getName().getPrefix(2), ndn::Name("/test/query-results"));
+      const std::string jsonRes(reinterpret_cast<const char*>(replyData->getContent().value()));
+      Json::Value parsedFromString;
+      Json::Reader reader;
+      BOOST_CHECK_EQUAL(reader.parse(jsonRes, parsedFromString), true);
+      BOOST_CHECK_EQUAL(parsedFromString["results"].size(), 3);
+      BOOST_CHECK_EQUAL(parsedFromString["results"][0], "/ndn/test1");
+      BOOST_CHECK_EQUAL(parsedFromString["results"][1], "/ndn/test2");
+      BOOST_CHECK_EQUAL(parsedFromString["results"][2], "/ndn/test3");
+    }
+  }
+
+  BOOST_AUTO_TEST_SUITE_END()
+
+}//tests
+}//atmos
diff --git a/catalog/tests/unit-tests/simple.cpp b/catalog/tests/unit-tests/simple.cpp
index 5dda75a..5921522 100644
--- a/catalog/tests/unit-tests/simple.cpp
+++ b/catalog/tests/unit-tests/simple.cpp
@@ -25,7 +25,7 @@
 #include <iostream>
 
 namespace atmos {
-namespace test {
+namespace tests {
 
 BOOST_AUTO_TEST_SUITE(MasterSuite)
 
@@ -53,5 +53,5 @@
 
 BOOST_AUTO_TEST_SUITE_END()
 
-} //namespace test
+} //namespace tests
 } //namespace atmos