From b388a5537f2e3b459021118fcda18e2a58da3492 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Sun, 21 Aug 2016 13:06:27 +0100 Subject: Add support for custom output serializers --- icespider/common/routes.ice | 9 +++++ icespider/compile/routeCompiler.cpp | 34 ++++++++++++++++ icespider/compile/routeCompiler.h | 2 + icespider/core/core.cpp | 1 - icespider/core/irouteHandler.cpp | 25 +++++++++--- icespider/core/irouteHandler.h | 5 ++- icespider/unittests/Jamfile.jam | 7 +++- icespider/unittests/testApp.cpp | 24 +++++++++++ icespider/unittests/testCompile.cpp | 15 +++++++ icespider/unittests/testRoutes.json | 7 ++++ icespider/unittests/xslt/transform.xslt | 14 +++++++ icespider/xslt/Jamfile.jam | 22 +++++++++++ icespider/xslt/exslt.cpp | 17 ++++++++ icespider/xslt/xsltStreamSerializer.cpp | 70 +++++++++++++++++++++++++++++++++ icespider/xslt/xsltStreamSerializer.h | 34 ++++++++++++++++ 15 files changed, 278 insertions(+), 8 deletions(-) create mode 100644 icespider/unittests/xslt/transform.xslt create mode 100644 icespider/xslt/Jamfile.jam create mode 100644 icespider/xslt/exslt.cpp create mode 100644 icespider/xslt/xsltStreamSerializer.cpp create mode 100644 icespider/xslt/xsltStreamSerializer.h diff --git a/icespider/common/routes.ice b/icespider/common/routes.ice index 65e7d67..bc2406a 100644 --- a/icespider/common/routes.ice +++ b/icespider/common/routes.ice @@ -20,12 +20,21 @@ module IceSpider { sequence Parameters; + class OutputSerializer { + string contentType; + string serializer; + StringSeq params; + }; + + sequence OutputSerializers; + class Route { string name; string path; HttpMethod method = GET; string operation; Parameters params; + OutputSerializers outputSerializers; }; sequence Routes; diff --git a/icespider/compile/routeCompiler.cpp b/icespider/compile/routeCompiler.cpp index 65b3141..8c274d8 100644 --- a/icespider/compile/routeCompiler.cpp +++ b/icespider/compile/routeCompiler.cpp @@ -192,6 +192,34 @@ namespace IceSpider { return rtn; } + void + RouteCompiler::registerOutputSerializers(FILE * output, RoutePtr r) const + { + for (const auto & os : r->outputSerializers) { + auto bs = boost::algorithm::replace_all_copy(os->serializer, ".", "::"); + auto slash = os->contentType.find('/'); + fprintbf(4, output, "addRouteSerializer({ \"%s\", \"%s\" }, new %s::IceSpiderFactory(", + os->contentType.substr(0, slash), os->contentType.substr(slash + 1), bs); + for (auto p = os->params.begin(); p != os->params.end(); ++p) { + if (p != os->params.begin()) { + fprintf(output, ", "); + } + fputs(p->c_str(), output); + } + fprintf(output, "));\n"); + } + } + + void + RouteCompiler::releaseOutputSerializers(FILE * output, RoutePtr r) const + { + for (const auto & os : r->outputSerializers) { + auto slash = os->contentType.find('/'); + fprintbf(4, output, "removeRouteSerializer({ \"%s\", \"%s\" });\n", + os->contentType.substr(0, slash), os->contentType.substr(slash + 1)); + } + } + void RouteCompiler::processConfiguration(FILE * output, RouteConfigurationPtr c, const Units & units) const { @@ -200,6 +228,7 @@ namespace IceSpider { fprintf(output, "// Standard headers.\n"); fprintf(output, "#include \n"); + fprintf(output, "#include \n"); fprintf(output, "\n// Interface headers.\n"); for (const auto & s : c->slices) { @@ -257,6 +286,11 @@ namespace IceSpider { } fprintf(output, "\n"); fprintbf(3, output, "{\n"); + registerOutputSerializers(output, r); + fprintbf(3, output, "}\n\n"); + fprintbf(3, output, "~%s()\n", r->name); + fprintbf(3, output, "{\n"); + releaseOutputSerializers(output, r); fprintbf(3, output, "}\n\n"); fprintbf(3, output, "void execute(IceSpider::IHttpRequest * request) const\n"); fprintbf(3, output, "{\n"); diff --git a/icespider/compile/routeCompiler.h b/icespider/compile/routeCompiler.h index af5912e..93522fd 100644 --- a/icespider/compile/routeCompiler.h +++ b/icespider/compile/routeCompiler.h @@ -26,6 +26,8 @@ namespace IceSpider { private: void processConfiguration(FILE * output, RouteConfigurationPtr, const Units &) const; + void registerOutputSerializers(FILE * output, RoutePtr) const; + void releaseOutputSerializers(FILE * output, RoutePtr) const; static Slice::OperationPtr findOperation(RoutePtr, const Units &); static Slice::OperationPtr findOperation(RoutePtr, const Slice::ContainerPtr &, const Ice::StringSeq & = Ice::StringSeq()); }; diff --git a/icespider/core/core.cpp b/icespider/core/core.cpp index 85a9bfa..b21a805 100644 --- a/icespider/core/core.cpp +++ b/icespider/core/core.cpp @@ -12,7 +12,6 @@ namespace IceSpider { // Initialize routes for (const auto & rp : AdHoc::PluginManager::getDefault()->getAll()) { auto r = rp->implementation(); - r->initialize(); auto & mroutes = routes[r->method]; if (mroutes.size() <= r->pathElementCount()) { mroutes.resize(r->pathElementCount() + 1); diff --git a/icespider/core/irouteHandler.cpp b/icespider/core/irouteHandler.cpp index dcbab7d..a0d8fcd 100644 --- a/icespider/core/irouteHandler.cpp +++ b/icespider/core/irouteHandler.cpp @@ -8,11 +8,6 @@ namespace IceSpider { IRouteHandler::IRouteHandler(HttpMethod m, const std::string & p) : Path(p), method(m) - { - } - - void - IRouteHandler::initialize() { auto globalSerializers = AdHoc::PluginManager::getDefault()->getAll(); for (const auto & gs : globalSerializers) { @@ -21,6 +16,10 @@ namespace IceSpider { } } + IRouteHandler::~IRouteHandler() + { + } + Ice::ObjectPrx IRouteHandler::getProxy(IHttpRequest * request, const char * type) const { @@ -44,5 +43,21 @@ namespace IceSpider { return Slicer::StreamSerializerFactory::createNew( "application/json", strm); } + + void + IRouteHandler::addRouteSerializer(const ContentType & ct, StreamSerializerFactoryPtr ssfp) + { + routeSerializers.insert({ ct, ssfp }); + } + + void + IRouteHandler::removeRouteSerializer(const ContentType & ct) + { + auto i = routeSerializers.find(ct); + if (i != routeSerializers.end()) { + delete i->second; + routeSerializers.erase(i); + } + } } diff --git a/icespider/core/irouteHandler.h b/icespider/core/irouteHandler.h index 6ee8160..1cfa5ee 100644 --- a/icespider/core/irouteHandler.h +++ b/icespider/core/irouteHandler.h @@ -13,9 +13,9 @@ namespace IceSpider { class DLL_PUBLIC IRouteHandler : public AdHoc::AbstractPluginImplementation, public Path { public: IRouteHandler(HttpMethod, const std::string & path); + virtual ~IRouteHandler(); virtual void execute(IHttpRequest * request) const = 0; - virtual void initialize(); virtual Slicer::SerializerPtr getSerializer(const char *, const char *, std::ostream &) const; virtual Slicer::SerializerPtr defaultSerializer(std::ostream &) const; @@ -41,6 +41,9 @@ namespace IceSpider { { return Interface::ProxyType::uncheckedCast(getProxy(request, typeid(Interface).name())); } + + void addRouteSerializer(const ContentType &, StreamSerializerFactoryPtr); + void removeRouteSerializer(const ContentType &); }; typedef AdHoc::PluginOf RouteHandlers; } diff --git a/icespider/unittests/Jamfile.jam b/icespider/unittests/Jamfile.jam index 234e715..05fecb4 100644 --- a/icespider/unittests/Jamfile.jam +++ b/icespider/unittests/Jamfile.jam @@ -46,6 +46,8 @@ run test-api dl adhocutil + slicer-xml + ../xslt//icespider-xslt ../core//icespider-core ../compile//icespider-compile ../compile//icespider-compile @@ -56,7 +58,9 @@ run run testApp.cpp testRoutes.json - : : : + : : + xslt/transform.xslt + : BOOST_TEST_DYN_LINK ../compile//icespider testCommon @@ -67,6 +71,7 @@ run slicer-xml ../common//icespider-common ../core//icespider-core + ../xslt//icespider-xslt ../common//icespider-common test-api ../compile diff --git a/icespider/unittests/testApp.cpp b/icespider/unittests/testApp.cpp index bb7c3c6..fb79b0a 100644 --- a/icespider/unittests/testApp.cpp +++ b/icespider/unittests/testApp.cpp @@ -9,9 +9,17 @@ #include #include #include +#include +#include using namespace IceSpider; +static void forceEarlyChangeDir() __attribute__((constructor(101))); +void forceEarlyChangeDir() +{ + boost::filesystem::current_path(XSTR(ROOT)); +} + BOOST_AUTO_TEST_CASE( testLoadConfiguration ) { BOOST_REQUIRE_EQUAL(6, AdHoc::PluginManager::getDefault()->getAll().size()); @@ -253,6 +261,22 @@ BOOST_AUTO_TEST_CASE( testCallIndexAcceptXml ) BOOST_REQUIRE_EQUAL(requestXml.output.str(), "Status: 200 OK\r\n\r\n\nindex\n"); } +BOOST_AUTO_TEST_CASE( testCallIndexAcceptTextHtml ) +{ + TestRequest requestHtml(this, HttpMethod::GET, "/"); + requestHtml.hdr["Accept"] = "text/html"; + process(&requestHtml); + BOOST_REQUIRE_EQUAL(requestHtml.output.str(), "Status: 200 OK\r\n\r\nSome Model

value: index

\n"); +} + +BOOST_AUTO_TEST_CASE( testCallViewSomethingAcceptHtml ) +{ + TestRequest requestHtml(this, HttpMethod::GET, "/view/something/1234"); + requestHtml.hdr["Accept"] = "text/html"; + process(&requestHtml); + BOOST_REQUIRE_EQUAL(requestHtml.output.str(), "Status: 406 Unacceptable\r\n\r\n"); +} + BOOST_AUTO_TEST_CASE( testCallIndexAcceptNotSupported ) { TestRequest requestBadAccept(this, HttpMethod::GET, "/"); diff --git a/icespider/unittests/testCompile.cpp b/icespider/unittests/testCompile.cpp index 411f703..46175ef 100644 --- a/icespider/unittests/testCompile.cpp +++ b/icespider/unittests/testCompile.cpp @@ -10,6 +10,12 @@ using namespace IceSpider; +static void forceEarlyChangeDir() __attribute__((constructor(101))); +void forceEarlyChangeDir() +{ + boost::filesystem::current_path(XSTR(ROOT)); +} + class CoreFixture { protected: CoreFixture() : @@ -73,9 +79,18 @@ BOOST_AUTO_TEST_CASE( testCompile ) auto compileCommand = boost::algorithm::join>({ "gcc", "-c", "-std=c++1y", "-fPIC", "-fvisibility=hidden", "-O3", "-I", "/usr/include/adhocutil", + "-I", "/usr/include/glib-2.0", + "-I", "/usr/include/glibmm-2.4", + "-I", "/usr/include/libxml2", + "-I", "/usr/include/libxml++-2.6", + "-I", "/usr/include/libxslt", "-I", "/usr/include/slicer", + "-I", "/usr/lib/glib-2.0/include", + "-I", "/usr/lib/glibmm-2.4/include", + "-I", "/usr/lib/libxml++-2.6/include", "-I", (rootDir.parent_path() / "core").string(), "-I", (rootDir.parent_path() / "common").string(), + "-I", (rootDir.parent_path() / "xslt").string(), "-I", (rootDir.parent_path() / "common" / "bin" / modeDir / "allow-ice-yes").string(), "-I", libGenDir.string(), "-o", outputo.string(), diff --git a/icespider/unittests/testRoutes.json b/icespider/unittests/testRoutes.json index cf5d289..921d983 100644 --- a/icespider/unittests/testRoutes.json +++ b/icespider/unittests/testRoutes.json @@ -6,6 +6,13 @@ "name": "index", "path": "/", "method": "GET", + "outputSerializers": [ + { + "contentType": "text/html", + "serializer": "IceSpider.XsltStreamSerializer", + "params": [ "\"xslt/transform.xslt\"" ] + } + ], "operation": "TestIceSpider.TestApi.index" }, { diff --git a/icespider/unittests/xslt/transform.xslt b/icespider/unittests/xslt/transform.xslt new file mode 100644 index 0000000..b70c90c --- /dev/null +++ b/icespider/unittests/xslt/transform.xslt @@ -0,0 +1,14 @@ + + + + + + + Some Model + + +

value:

+ + +
+
diff --git a/icespider/xslt/Jamfile.jam b/icespider/xslt/Jamfile.jam new file mode 100644 index 0000000..1bf62a8 --- /dev/null +++ b/icespider/xslt/Jamfile.jam @@ -0,0 +1,22 @@ +lib adhocutil : : : : /usr/include/adhocutil ; +lib slicer-xml : : : : /usr/include/slicer ; +lib boost_system ; +lib IceUtil ; +lib xslt ; +lib exslt ; + +lib icespider-xslt : + [ glob-tree *.cpp : bin ] + : + adhocutil + slicer-xml + boost_system + IceUtil + xslt + exslt + ../..//libxmlpp + : : + . + ../..//libxmlpp + ; + diff --git a/icespider/xslt/exslt.cpp b/icespider/xslt/exslt.cpp new file mode 100644 index 0000000..63bd0d1 --- /dev/null +++ b/icespider/xslt/exslt.cpp @@ -0,0 +1,17 @@ +#include +#include + +static void initLibXml() __attribute__((constructor(102))); +void initLibXml() +{ + xmlInitParser(); + exsltRegisterAll(); +} + +static void cleanupLibXml() __attribute__((destructor(102))); +void cleanupLibXml() +{ + xsltCleanupGlobals(); + xmlCleanupParser(); +} + diff --git a/icespider/xslt/xsltStreamSerializer.cpp b/icespider/xslt/xsltStreamSerializer.cpp new file mode 100644 index 0000000..bbe3943 --- /dev/null +++ b/icespider/xslt/xsltStreamSerializer.cpp @@ -0,0 +1,70 @@ +#include "xsltStreamSerializer.h" +#include +#include +#include + +namespace IceSpider { + static int xmlstrmclosecallback(void * context) + { + ((std::ostream*)context)->flush(); + return 0; + } + + static int xmlstrmwritecallback(void * context, const char * buffer, int len) + { + ((std::ostream*)context)->write(buffer, len); + return len; + } + + XsltStreamSerializer::IceSpiderFactory::IceSpiderFactory(const char * path) : + stylesheet(xsltParseStylesheetFile(BAD_CAST path)) + { + if (!stylesheet) { + throw xmlpp::exception("Failed to load stylesheet"); + } + } + + XsltStreamSerializer::IceSpiderFactory::~IceSpiderFactory() + { + xsltFreeStylesheet(stylesheet); + } + + XsltStreamSerializer * + XsltStreamSerializer::IceSpiderFactory::create(std::ostream & strm) const + { + return new XsltStreamSerializer(strm, stylesheet); + } + + XsltStreamSerializer::XsltStreamSerializer(std::ostream & os, xsltStylesheet * ss) : + Slicer::XmlDocumentSerializer(doc), + strm(os), + doc(nullptr), + stylesheet(ss) + { + } + + XsltStreamSerializer::~XsltStreamSerializer() + { + delete doc; + } + + void + XsltStreamSerializer::Serialize(Slicer::ModelPartPtr mp) + { + Slicer::XmlDocumentSerializer::Serialize(mp); + auto result = xsltApplyStylesheet(stylesheet, doc->cobj(), nullptr); + if (!result) { + throw xmlpp::exception("Failed to apply XSL transform"); + } + xmlOutputBufferPtr buf = xmlOutputBufferCreateIO(xmlstrmwritecallback, xmlstrmclosecallback, &strm, NULL); + if (xmlStrcmp(stylesheet->method, BAD_CAST "html") == 0) { + htmlDocContentDumpFormatOutput(buf, result, (const char *) stylesheet->encoding, 0); + } + else { + xmlNodeDumpOutput(buf, result, result->children, 0, 0, (const char *) stylesheet->encoding); + } + xmlOutputBufferClose(buf); + xmlFreeDoc(result); + } +} + diff --git a/icespider/xslt/xsltStreamSerializer.h b/icespider/xslt/xsltStreamSerializer.h new file mode 100644 index 0000000..d8000d1 --- /dev/null +++ b/icespider/xslt/xsltStreamSerializer.h @@ -0,0 +1,34 @@ +#ifndef ICESPIDER_CORE_XSLTSTREAMSERIALIZER_H +#define ICESPIDER_CORE_XSLTSTREAMSERIALIZER_H + +#include +#include +#include + +namespace IceSpider { + class DLL_PUBLIC XsltStreamSerializer : public Slicer::XmlDocumentSerializer { + public: + class IceSpiderFactory : public Slicer::StreamSerializerFactory { + public: + IceSpiderFactory(const char *); + ~IceSpiderFactory(); + + XsltStreamSerializer * create(std::ostream &) const override; + + xsltStylesheet * stylesheet; + }; + + XsltStreamSerializer(std::ostream &, xsltStylesheet *); + ~XsltStreamSerializer(); + + void Serialize(Slicer::ModelPartPtr mp) override; + + protected: + std::ostream & strm; + xmlpp::Document * doc; + xsltStylesheet * stylesheet; + }; +} + +#endif + -- cgit v1.2.3