diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b1f621f7a1baaab963d5d36b6a06d327c7cdde1..4324612e0bc3cc61cc98d19ad596bdadf9baf788 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 2.8.12) - +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/CMakeModules/") set(IRODS_VERSION "4.1.x") message(STATUS "Building for irods-server version ${IRODS_VERSION}") @@ -35,6 +35,12 @@ include_directories(${CURL_INCLUDE_DIRS}) find_package(LibXml2 REQUIRED) include_directories(${LIBXML2_INCLUDE_DIR}) +find_package(LibXslt REQUIRED) +include_directories(${LIBXSLT_INCLUDE_DIR}) + +find_package(Jansson REQUIRED) +include_directories(${JANSSON_INCLUDE_DIRS}) + include_directories("/usr/include/irods") add_library(msiSetUpperCaseWhereQuery SHARED src/msiSetUpperCaseWhereQuery.cc) @@ -43,12 +49,19 @@ add_library(msiRegisterDataCiteDOI SHARED src/msiRegisterDataCiteDOI.cc) add_library(msiGenerateRandomID SHARED src/msiGenerateRandomID.cc) add_library(msiGetDataCiteDOI SHARED src/msiGetDataCiteDOI.cc) add_library(msiLoadMetadataFromXml SHARED src/msiLoadMetadataFromXml.cc) - -target_link_libraries(msiStrToUpper LINK_PUBLIC ${Boost_LIBRARIES}) -target_link_libraries(msiRegisterDataCiteDOI LINK_PUBLIC ${CURL_LIBRARIES}) -target_link_libraries(msiGetDataCiteDOI LINK_PUBLIC ${CURL_LIBRARIES}) -target_link_libraries(msiLoadMetadataFromXml LINK_PUBLIC ${LIBXML2_LIBRARIES}) - +add_library(msiXmlDocSchemaValidate SHARED src/msiXmlDocSchemaValidate.cc) +add_library(msiXsltApply SHARED src/msiXsltApply.cc) +add_library(msi_json_arrayops SHARED src/msi_json_arrayops.cc) +add_library(msi_json_objops SHARED src/msi_json_objops.cc) + +target_link_libraries(msiStrToUpper LINK_PUBLIC ${Boost_LIBRARIES}) +target_link_libraries(msiRegisterDataCiteDOI LINK_PUBLIC ${CURL_LIBRARIES}) +target_link_libraries(msiGetDataCiteDOI LINK_PUBLIC ${CURL_LIBRARIES}) +target_link_libraries(msiLoadMetadataFromXml LINK_PUBLIC ${LIBXML2_LIBRARIES}) +target_link_libraries(msiXmlDocSchemaValidate LINK_PUBLIC ${LIBXML2_LIBRARIES}) +target_link_libraries(msiXsltApply LINK_PUBLIC ${LIBXML2_LIBRARIES} ${LIBXSLT_LIBRARIES} ${LIBXSLT_EXSLT_LIBRARIES}) +target_link_libraries(msi_json_arrayops LINK_PUBLIC ${JANSSON_LIBRARIES} ${Boost_LIBRARIES}) +target_link_libraries(msi_json_objops LINK_PUBLIC ${JANSSON_LIBRARIES} ${Boost_LIBRARIES}) install(TARGETS msiSetUpperCaseWhereQuery @@ -57,6 +70,10 @@ install(TARGETS msiGenerateRandomID msiGetDataCiteDOI msiLoadMetadataFromXml + msiXmlDocSchemaValidate + msiXsltApply + msi_json_arrayops + msi_json_objops DESTINATION /var/lib/irods/plugins/microservices) @@ -75,7 +92,7 @@ set(CPACK_GENERATOR "RPM") set(CPACK_PACKAGE_NAME "irods-uu-microservices") set(CPACK_PACKAGE_VENDOR "Utrecht University <fbyoda@uu.nl>") set(CPACK_PACKAGE_CONTACT "Utrecht University <fbyoda@uu.nl>") -set(CPACK_PACKAGE_VERSION "4.1.10_0.1.1") +set(CPACK_PACKAGE_VERSION "4.1.11_0.3.0") set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/package/description.txt") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Miscellaneous microservices developed by Utrecht university.") @@ -87,6 +104,7 @@ set(CPACK_RPM_PACKAGE_LICENSE "LGPLv3") set(CPACK_RPM_PACKAGE_REQUIRES "boost-locale >= 1.51") set(CPACK_RPM_PACKAGE_REQUIRES "libcurl >= 7.29.0") set(CPACK_RPM_PACKAGE_REQUIRES "libxml2 >= 2.9.1") +set(CPACK_RPM_PACKAGE_REQUIRES "libxslt >= 1.1.28") set(CPACK_RPM_PACKAGE_URL "https://github.com/UtrechtUniversity/irods-uu-microservices") set(CPACK_RPM_PACKAGE_AUTOREQ 0) set(CPACK_RPM_PACKAGE_AUTOPROV 0) diff --git a/CMakeModules/FindJansson.cmake b/CMakeModules/FindJansson.cmake new file mode 100644 index 0000000000000000000000000000000000000000..bc0fdac32283baedd69e165d82062af4b0d00d00 --- /dev/null +++ b/CMakeModules/FindJansson.cmake @@ -0,0 +1,57 @@ +# - Try to find Jansson +# Once done this will define +# +# JANSSON_FOUND - system has Jansson +# JANSSON_INCLUDE_DIRS - the Jansson include directory +# JANSSON_LIBRARIES - Link these to use Jansson +# +# Copyright (c) 2011 Lee Hambley <lee.hambley@gmail.com> +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +if (JANSSON_LIBRARIES AND JANSSON_INCLUDE_DIRS) + # in cache already + set(JANSSON_FOUND TRUE) +else (JANSSON_LIBRARIES AND JANSSON_INCLUDE_DIRS) + find_path(JANSSON_INCLUDE_DIR + NAMES + jansson.h + PATHS + /usr/include + /usr/local/include + /opt/local/include + /sw/include + ) + +find_library(JANSSON_LIBRARY + NAMES + jansson + PATHS + /usr/lib + /usr/local/lib + /opt/local/lib + /sw/lib + ) + +set(JANSSON_INCLUDE_DIRS + ${JANSSON_INCLUDE_DIR} + ) + +if (JANSSON_LIBRARY) + set(JANSSON_LIBRARIES + ${JANSSON_LIBRARIES} + ${JANSSON_LIBRARY} + ) +endif (JANSSON_LIBRARY) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Jansson DEFAULT_MSG + JANSSON_LIBRARIES JANSSON_INCLUDE_DIRS) + + # show the JANSSON_INCLUDE_DIRS and JANSSON_LIBRARIES variables only in the advanced view + mark_as_advanced(JANSSON_INCLUDE_DIRS JANSSON_LIBRARIES) + +endif (JANSSON_LIBRARIES AND JANSSON_INCLUDE_DIRS) diff --git a/README.md b/README.md index fce75c317faae71b3377ce33c5649b98ca1521b0..442a7c61f66e10c4ba873c5e627c5a11a5edb39c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,35 @@ # irods-uu-microservices -Miscellaneous microservices developed by Utrecht university. +Miscellaneous iRODS microservices developed or modified by Utrecht university. + + +msi\_json\_arrayops and msi\_json\_objops microservices are derived from +work from the Donders Institute. The license below applies + +### Copyright and license for msi_json_arrayops and msi_json_objops + +This microservice has kindly been provided by the Donders Institute for +Brain, Cognition and Behaviour. + +Copyright (c) 2016, Radboud University, Nijmegen, The Netherlands +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/package/description.txt b/package/description.txt index 821fdeae99288574d30d0def5a6e7341c71add4f..af0984ac9bde7131ad741e9c591dae0663e9e586 100644 --- a/package/description.txt +++ b/package/description.txt @@ -1 +1,2 @@ -Miscellaneous microservices developed by Utrecht university. +Miscellaneous microservices developed or modified by Utrecht university. + diff --git a/src/msiXmlDocSchemaValidate.cc b/src/msiXmlDocSchemaValidate.cc new file mode 100644 index 0000000000000000000000000000000000000000..5a828fc5112211916b4d258122ed6b5459bc6a7b --- /dev/null +++ b/src/msiXmlDocSchemaValidate.cc @@ -0,0 +1,370 @@ +/** + * @file msiXmlDocSchemaValidate.cpp + * + */ + +/*** Copyright (c), The Regents of the University of California *** + *** For more information please refer to files in the COPYRIGHT directory ***/ + +#include <libxml/tree.h> +#include <libxml/parser.h> +#include <libxml/xmlschemas.h> +#include <libxml/uri.h> +#include "apiHeaderAll.hpp" +#include "msParam.hpp" +#include "reGlobalsExtern.hpp" +#include "irods_ms_plugin.hpp" +#include "irods_server_api_call.hpp" +#include "microservice.hpp" +#include "objMetaOpr.hpp" +#include "miscUtil.h" + + + + +extern "C" { + + +/* custom handler to catch XML validation errors and print them to a buffer */ +static int +myErrorCallback(bytesBuf_t *errBuf, const char* errMsg, ...) +{ + va_list ap; + char tmpStr[MAX_NAME_LEN]; + int written; + + va_start(ap, errMsg); + written = vsnprintf(tmpStr, MAX_NAME_LEN, errMsg, ap); + va_end(ap); + + appendToByteBuf(errBuf, tmpStr); + return (written); +} + + +/** + * \fn msiXmlDocSchemaValidate(msParam_t *xmlObj, msParam_t *xsdObj, msParam_t *status, ruleExecInfo_t *rei) + * + * \brief This microservice validates an XML file against an XSD schema, both iRODS objects. + * + * \module xml + * + * \since pre-2.1 + * + * \author Antoine de Torcy + * \date 2008/05/29 + * + * \usage See clients/icommands/test/rules3.0/ + * + * \param[in] xmlObj - a msParam of type DataObjInp_MS_T or STR_MS_T which is irods path of the XML object. + * \param[in] xsdObj - a msParam of type DataObjInp_MS_T or STR_MS_T which is irods path of the XSD object. + * \param[out] status - a msParam of type INT_MS_T which is a validation result. + * \param[in,out] rei - The RuleExecInfo structure that is automatically + * handled by the rule engine. The user does not include rei as a + * parameter in the rule invocation. + * + * \DolVarDependence None + * \DolVarModified None + * \iCatAttrDependence None + * \iCatAttrModified None + * \sideeffect None + * + * \return integer + * \retval 0 on success + * \pre None + * \post None + * \sa None +**/ +int +msiXmlDocSchemaValidate(msParam_t *xmlObj, msParam_t *xsdObj, msParam_t *status, ruleExecInfo_t *rei) +{ + /* for parsing msParams and to open iRODS objects */ + dataObjInp_t xmlObjInp, *myXmlObjInp; + dataObjInp_t xsdObjInp, *myXsdObjInp; + int xmlObjID, xsdObjID; + + /* for getting size of objects to read from */ + rodsObjStat_t *rodsObjStatOut = NULL; + + /* for reading from iRODS objects */ + openedDataObjInp_t openedDataObjInp; + bytesBuf_t *xmlBuf = NULL; + xmlChar* xmlXmlChar; + xmlChar* xsdXmlChar; + + /* for xml parsing and validating */ + xmlDocPtr doc, xsd_doc; + xmlSchemaParserCtxtPtr parser_ctxt; + xmlSchemaPtr schema; + xmlSchemaValidCtxtPtr valid_ctxt; + bytesBuf_t *errBuf; + + /* misc. to avoid repeating rei->rsComm */ + rsComm_t *rsComm; + + + + /************************************* USUAL INIT PROCEDURE **********************************/ + + /* For testing mode when used with irule --test */ + RE_TEST_MACRO (" Calling msiXmlDocSchemaValidate") + + + /* Sanity checks */ + if (rei == NULL || rei->rsComm == NULL) + { + rodsLog (LOG_ERROR, "msiXmlDocSchemaValidate: input rei or rsComm is NULL."); + return (SYS_INTERNAL_NULL_INPUT_ERR); + } + + rsComm = rei->rsComm; + + + + /************************************ ADDITIONAL INIT SETTINGS *********************************/ + + /* XML constants */ + xmlSubstituteEntitiesDefault(1); + xmlLoadExtDtdDefaultValue = 1; + + + /* allocate memory for output error buffer */ + errBuf = (bytesBuf_t *)malloc(sizeof(bytesBuf_t)); + errBuf->buf = strdup(""); + errBuf->len = strlen((char*)errBuf->buf); + + /* Default status is failure, overwrite if success */ + fillBufLenInMsParam (status, -1, NULL); + + + /********************************** RETRIEVE INPUT PARAMS **************************************/ + + /* Get path of XML document */ + rei->status = parseMspForDataObjInp (xmlObj, &xmlObjInp, &myXmlObjInp, 0); + if (rei->status < 0) + { + rodsLog (LOG_ERROR, "msiXmlDocSchemaValidate: input xmlObj error. status = %d", rei->status); + free(errBuf); + return (rei->status); + } + + + /* Get path of schema */ + rei->status = parseMspForDataObjInp (xsdObj, &xsdObjInp, &myXsdObjInp, 0); + if (rei->status < 0) + { + rodsLog (LOG_ERROR, "msiXmlDocSchemaValidate: input xsdObj error. status = %d", rei->status); + free(errBuf); + return (rei->status); + } + + + /******************************** OPEN AND READ FROM XML OBJECT ********************************/ + + /* Open XML file */ + if ((xmlObjID = rsDataObjOpen(rsComm, &xmlObjInp)) < 0) + { + rodsLog (LOG_ERROR, "msiXmlDocSchemaValidate: Cannot open XML data object. status = %d", xmlObjID); + free(errBuf); + return (xmlObjID); + } + + + /* Get size of XML file */ + rei->status = rsObjStat (rsComm, &xmlObjInp, &rodsObjStatOut); + if (rei->status < 0 || !rodsObjStatOut) + { + rodsLog (LOG_ERROR, "msiXmlDocSchemaValidate: Cannot stat XML data object. status = %d", rei->status); + free(errBuf); + return (rei->status); + } + + + /* xmlBuf init */ + /* memory for xmlBuf->buf is allocated in rsFileRead() */ + xmlBuf = (bytesBuf_t *) malloc (sizeof (bytesBuf_t)); + memset (xmlBuf, 0, sizeof (bytesBuf_t)); + + + /* Read content of XML file */ + memset (&openedDataObjInp, 0, sizeof (openedDataObjInp_t)); + openedDataObjInp.l1descInx = xmlObjID; + openedDataObjInp.len = (int)rodsObjStatOut->objSize; + + rei->status = rsDataObjRead (rsComm, &openedDataObjInp, xmlBuf); + + xmlXmlChar = xmlCharStrndup((char*)xmlBuf->buf, xmlBuf->len); + + /* Close XML file */ + rei->status = rsDataObjClose (rsComm, &openedDataObjInp); + + /* cleanup */ + freeRodsObjStat (rodsObjStatOut); + + /* content of xmlBuf can be discarded */ + clearBBuf(xmlBuf); + + + /*************************************** PARSE XML DOCUMENT **************************************/ + + /* Parse xmlBuf.buf into an xmlDocPtr */ + doc = xmlParseDoc(xmlXmlChar); + + if (doc == NULL) + { + rodsLog (LOG_ERROR, "msiXmlDocSchemaValidate: XML document cannot be loaded or is not well-formed."); + free(errBuf); + /* Don't call xmlCleanupParser when libxml2 will be used in other microservice calls + xmlCleanupParser(); + */ + + return (USER_INPUT_FORMAT_ERR); + } + + + + /******************************** OPEN AND READ FROM XSD OBJECT ********************************/ + + /* Open schema file */ + if ((xsdObjID = rsDataObjOpen(rsComm, &xsdObjInp)) < 0) + { + rodsLog (LOG_ERROR, "msiXmlDocSchemaValidate: Cannot open XSD data object. status = %d", xsdObjID); + free(errBuf); + xmlFreeDoc(doc); + /* Don't call xmlCleanupParser when libxml2 will be used in other microservice calls + xmlCleanupParser(); + */ + return (xsdObjID); + } + + + /* Get size of schema file */ + rei->status = rsObjStat (rsComm, &xsdObjInp, &rodsObjStatOut); + + + /* Read entire schema file */ + memset (&openedDataObjInp, 0, sizeof (openedDataObjInp_t)); + openedDataObjInp.l1descInx = xsdObjID; + openedDataObjInp.len = (int)rodsObjStatOut->objSize; + + rei->status = rsDataObjRead (rsComm, &openedDataObjInp, xmlBuf); + + xsdXmlChar = xmlCharStrndup((char*)xmlBuf->buf, xmlBuf->len); + + /* Close schema file */ + rei->status = rsDataObjClose (rsComm, &openedDataObjInp); + + /* cleanup */ + freeRodsObjStat (rodsObjStatOut); + + /* xmlBuf is no longer needed */ + freeBBuf(xmlBuf); + + /*************************************** PARSE XSD DOCUMENT **************************************/ + + xsd_doc = xmlParseDoc(xsdXmlChar); + + if (xsd_doc == NULL) + { + rodsLog (LOG_ERROR, "msiXmlDocSchemaValidate: XML Schema cannot be loaded or is not well-formed."); + free(errBuf); + xmlFreeDoc(doc); + /* Don't call xmlCleanupParser when libxml2 will be used in other microservice calls + xmlCleanupParser(); + */ + return (USER_INPUT_FORMAT_ERR); + } + + + + /**************************************** VALIDATE DOCUMENT **************************************/ + + /* Create a parser context */ + parser_ctxt = xmlSchemaNewDocParserCtxt(xsd_doc); + if (parser_ctxt == NULL) + { + rodsLog (LOG_ERROR, "msiXmlDocSchemaValidate: Unable to create a parser context for the schema."); + free(errBuf); + xmlFreeDoc(xsd_doc); + xmlFreeDoc(doc); + /* Don't call xmlCleanupParser when libxml2 will be used in other microservice calls + xmlCleanupParser(); + */ + return (USER_INPUT_FORMAT_ERR); + } + + + /* Parse the XML schema */ + schema = xmlSchemaParse(parser_ctxt); + if (schema == NULL) + { + rodsLog (LOG_ERROR, "msiXmlDocSchemaValidate: Invalid schema."); + free(errBuf); + xmlSchemaFreeParserCtxt(parser_ctxt); + xmlFreeDoc(doc); + xmlFreeDoc(xsd_doc); + + /* Don't call xmlCleanupParser when libxml2 will be used in other microservice calls + xmlCleanupParser(); + */ + + return (USER_INPUT_FORMAT_ERR); + } + + + /* Create a validation context */ + valid_ctxt = xmlSchemaNewValidCtxt(schema); + if (valid_ctxt == NULL) + { + rodsLog (LOG_ERROR, "msiXmlDocSchemaValidate: Unable to create a validation context for the schema."); + free(errBuf); + xmlSchemaFree(schema); + xmlSchemaFreeParserCtxt(parser_ctxt); + xmlFreeDoc(xsd_doc); + xmlFreeDoc(doc); + /* Don't call when libxml2 is used by other microservice calls + xmlCleanupParser(); + */ + return (USER_INPUT_FORMAT_ERR); + } + + + /* Set myErrorCallback() as the default handler for error messages and warnings */ + xmlSchemaSetValidErrors(valid_ctxt, (xmlSchemaValidityErrorFunc)myErrorCallback, (xmlSchemaValidityWarningFunc)myErrorCallback, errBuf); + + + /* Validate XML doc */ + rei->status = xmlSchemaValidateDoc(valid_ctxt, doc); + + + + /******************************************* WE'RE DONE ******************************************/ + + /* return both error code and messages through status */ + resetMsParam (status); + fillBufLenInMsParam (status, rei->status, errBuf); + + + /* cleanup of all xml parsing stuff */ + xmlSchemaFreeValidCtxt(valid_ctxt); + xmlSchemaFree(schema); + xmlSchemaFreeParserCtxt(parser_ctxt); + xmlFreeDoc(doc); + xmlFreeDoc(xsd_doc); + + /* Don't call when libxml2 is used by other microservice calls + xmlCleanupParser(); + */ + + return (rei->status); +} + + +irods::ms_table_entry * plugin_factory() { + irods::ms_table_entry* msvc = new irods::ms_table_entry(3); + msvc->add_operation("msiXmlDocSchemaValidate","msiXmlDocSchemaValidate"); + return msvc; +} + +} // extern "C" + diff --git a/src/msiXsltApply.cc b/src/msiXsltApply.cc new file mode 100644 index 0000000000000000000000000000000000000000..dc68f361292480ecb180c9027762e8462104115a --- /dev/null +++ b/src/msiXsltApply.cc @@ -0,0 +1,272 @@ +/** + * @file xsltMS.c + * + */ + +/*** Copyright (c), The Regents of the University of California *** + *** For more information please refer to files in the COPYRIGHT directory ***/ +#include <libxslt/transform.h> +#include <libxslt/xsltutils.h> +#include <libxslt/xsltInternals.h> +#include <libexslt/exslt.h> +#include <libexslt/exsltconfig.h> +#include <iostream> +#include "apiHeaderAll.hpp" +#include "msParam.hpp" +#include "reGlobalsExtern.hpp" +#include "irods_ms_plugin.hpp" +#include "irods_server_api_call.hpp" +#include "microservice.hpp" +#include "objMetaOpr.hpp" +#include "miscUtil.h" + + +extern int xmlLoadExtDtdDefaultValue; + +extern "C" { + + /** + * \fn msiXsltApply (msParam_t *xsltObj, msParam_t *xmlObj, msParam_t *msParamOut, ruleExecInfo_t *rei) + * + * \brief This function applies an XSL stylesheet to an XML file, both existing iRODS objects. + * + * \module XML + * + * \since pre-2.1 + * + * \author Antoine de Torcy + * \date 2008/05/29 + * + * \usage See clients/icommands/test/rules3.0/ + * + * \param[in] xsltObj - a msParam of type DataObjInp_MS_T or STR_MS_T + * \param[in] xmlObj - a msParam of type DataObjInp_MS_T or STR_MS_T + * \param[out] msParamOut - a msParam of operation status BUF_LEN_MS_T + * \param[in,out] rei - The RuleExecInfo structure that is automatically + * handled by the rule engine. The user does not include rei as a + * parameter in the rule invocation. + * + * \DolVarDependence None + * \DolVarModified None + * \iCatAttrDependence None + * \iCatAttrModified None + * \sideeffect None + * + * \return integer + * \retval 0 on success + * \pre None + * \post None + * \sa None + **/ + int + msiXsltApply(msParam_t *xsltObj, msParam_t *xmlObj, msParam_t *msParamOut, ruleExecInfo_t *rei) + { + /* for parsing msParams and to open iRODS objects */ + dataObjInp_t xmlDataObjInp, *myXmlDataObjInp; + dataObjInp_t xsltDataObjInp, *myXsltDataObjInp; + int xmlObjID, xsltObjID; + + /* for getting size of objects to read from */ + rodsObjStat_t *rodsObjStatOut = NULL; + + /* for reading from iRODS objects */ + openedDataObjInp_t openedDataObjInp; + bytesBuf_t *xmlBuf = NULL, *xsltBuf = NULL; + + /* misc. to avoid repeating rei->rsComm */ + rsComm_t *rsComm; + + /* for xml parsing */ + char *outStr; + int outLen; + xsltStylesheetPtr style = NULL; + xmlDocPtr xslSheet, doc, res; + + /* for output buffer */ + bytesBuf_t *mybuf; + + + /********************************* USUAL INIT PROCEDURE **********************************/ + + /* For testing mode when used with irule --test */ + RE_TEST_MACRO (" Calling msiXsltApply") + + + /* Sanity checks */ + if (rei == NULL || rei->rsComm == NULL) + { + rodsLog (LOG_ERROR, "msiXsltApply: input rei or rsComm is NULL."); + return (SYS_INTERNAL_NULL_INPUT_ERR); + } + + rsComm = rei->rsComm; + + + + /********************************** RETRIEVE INPUT PARAMS **********************************/ + + /* Get xsltObj: the XSLT stylesheet */ + rei->status = parseMspForDataObjInp (xsltObj, &xsltDataObjInp, &myXsltDataObjInp, 0); + if (rei->status < 0) + { + rodsLog (LOG_ERROR, "msiXsltApply: input xsltObj error. status = %d", rei->status); + return (rei->status); + } + + + /* Get xmlObj: the XML document */ + rei->status = parseMspForDataObjInp (xmlObj, &xmlDataObjInp, &myXmlDataObjInp, 0); + if (rei->status < 0) + { + rodsLog (LOG_ERROR, "msiXsltApply: input xmlObj error. status = %d", rei->status); + return (rei->status); + } + + + + /******************************** GET CONTENTS OF IRODS OBJS ********************************/ + + /* Open XSLT file */ + if ((xsltObjID = rsDataObjOpen(rsComm, &xsltDataObjInp)) < 0) + { + rodsLog (LOG_ERROR, "msiXsltApply: Cannot open XSLT data object. status = %d", xsltObjID); + return (xsltObjID); + } + + + /* Get size of XSLT file */ + rei->status = rsObjStat (rsComm, &xsltDataObjInp, &rodsObjStatOut); + if( NULL == rodsObjStatOut ) { // JMC cppcheck nullptr ref + rodsLog( LOG_ERROR, "msiXsltApply:: null &rodsObjStatOut" ); + return ( rei->status ); + } + + /* xsltBuf init */ + /* memory for xsltBuf->buf is allocated in rsFileRead() */ + xsltBuf = (bytesBuf_t *) malloc (sizeof (bytesBuf_t)); + memset (xsltBuf, 0, sizeof (bytesBuf_t)); + + + /* Read XSLT file */ + memset (&openedDataObjInp, 0, sizeof (openedDataObjInp_t)); + openedDataObjInp.l1descInx = xsltObjID; + openedDataObjInp.len = (int)rodsObjStatOut->objSize; + + rei->status = rsDataObjRead (rsComm, &openedDataObjInp, xsltBuf); + + /* Convert buffer to xmlChar (null-terminated) */ + + xmlChar* xsltXmlChar = xmlCharStrndup((char*)xsltBuf->buf, openedDataObjInp.len); + + /* Close XSLT file */ + rei->status = rsDataObjClose (rsComm, &openedDataObjInp); + + + /* Cleanup. Needed before using rodsObjStatOut for a new rsObjStat() call */ + freeRodsObjStat (rodsObjStatOut); + + /* xsltBuf is no longer needed */ + freeBBuf(xsltBuf); + + + /* Open XML file */ + if ((xmlObjID = rsDataObjOpen(rsComm, &xmlDataObjInp)) < 0) + { + rodsLog (LOG_ERROR, "msiXsltApply: Cannot open XML data object. status = %d", xmlObjID); + return (xmlObjID); + } + + + /* Get size of XML file */ + rei->status = rsObjStat (rsComm, &xmlDataObjInp, &rodsObjStatOut); + + + /* xmlBuf init */ + /* memory for xmlBuf->buf is allocated in rsFileRead() */ + xmlBuf = (bytesBuf_t *) malloc (sizeof (bytesBuf_t)); + memset (xmlBuf, 0, sizeof (bytesBuf_t)); + + + /* Read XML file */ + memset (&openedDataObjInp, 0, sizeof (openedDataObjInp_t)); + openedDataObjInp.l1descInx = xmlObjID; + openedDataObjInp.len = (int)rodsObjStatOut->objSize; + + rei->status = rsDataObjRead (rsComm, &openedDataObjInp, xmlBuf); + + /* copy buffer into null terminated xmlChar */ + xmlChar* xmlXmlChar = xmlCharStrndup((char*)xmlBuf->buf, openedDataObjInp.len); + + /* Close XML file */ + rei->status = rsDataObjClose (rsComm, &openedDataObjInp); + + /* cleanup */ + freeRodsObjStat (rodsObjStatOut); + /* xmlBuf is no longer needed */ + freeBBuf(xmlBuf); + + + /******************************** PARSE XML DOCS AND APPLY XSL ********************************/ + + xmlSubstituteEntitiesDefault(1); + xmlLoadExtDtdDefaultValue = 1; + + /* register all available exslt extensions. This includes str:encode-uri */ + exsltRegisterAll(); + + /* Parse xsltXmlChar into an xmlDocPtr, and the xmlDocPtr into an xsltStylesheetPtr */ + xslSheet = xmlParseDoc(xsltXmlChar); + style = xsltParseStylesheetDoc(xslSheet); + + doc = xmlParseDoc(xmlXmlChar); + + /* And the magic happens */ + res = xsltApplyStylesheet(style, doc, NULL); + + if (res == NULL) { + rei->status = XML_PARSING_ERR; + } else { + /* Save result XML document to a string */ + rei->status = xsltSaveResultToString((xmlChar**)&outStr, &outLen, res, style); + } + + + /* cleanup of all xml parsing stuff */ + xsltFreeStylesheet(style); + xmlFreeDoc(res); + xmlFreeDoc(doc); + + /* don't call if libxslt or libxml2 are called in other microservices + xsltCleanupGlobals(); + xmlCleanupParser(); + */ + + /************************************** WE'RE DONE **************************************/ + + if (rei->status != XML_PARSING_ERR) { + /* copy the result string into an output buffer */ + mybuf = (bytesBuf_t *)malloc(sizeof(bytesBuf_t)); + mybuf->buf = (void *)outStr; + mybuf->len = strlen(outStr); + + + /* send results out to msParamOut */ + fillBufLenInMsParam (msParamOut, mybuf->len, mybuf); + } + + return (rei->status); + } + + + irods::ms_table_entry* plugin_factory() { + irods::ms_table_entry* msvc = new irods::ms_table_entry(3); + + msvc->add_operation( "msiXsltApply", "msiXsltApply" ); + + return msvc; + } + + + +}; // extern "C" + diff --git a/src/msi_json_arrayops.cc b/src/msi_json_arrayops.cc new file mode 100644 index 0000000000000000000000000000000000000000..91021da5520aa2685a5b2b2e213ccc50ba22b33b --- /dev/null +++ b/src/msi_json_arrayops.cc @@ -0,0 +1,131 @@ +// =-=-=-=-=-=-=- +#include "apiHeaderAll.hpp" +#include "msParam.hpp" +#include "reGlobalsExtern.hpp" +#include "irods_ms_plugin.hpp" +#include "reFuncDefs.hpp" +#include "jansson.h" + +// =-=-=-=-=-=-=- +// STL/boost Includes +#include <string> +#include <iostream> +#include <vector> +//#include <boost/algorithm/string.hpp> + +extern "C" { + // =-=-=-=-=-=-=- + int msi_json_arrayops_impl(msParam_t* json_str, msParam_t* val, msParam_t* ops, msParam_t* sizeOrIndex, ruleExecInfo_t* rei) { + using std::cout; + using std::endl; + using std::string; + + // input type check + const char *inJsonStr = parseMspForStr( json_str ); + const char *inVal = parseMspForStr( val ); + const char *inOps = parseMspForStr( ops ); + const int inIdx = parseMspForPosInt( sizeOrIndex ); + + if( ! inJsonStr ) { + cout << "msi_json_arrayops - invalid inJsonStr" << endl; + return SYS_INVALID_INPUT_PARAM; + } + if( ! inOps ) { + cout << "msi_json_arrayops - invalid inOps" << endl; + return SYS_INVALID_INPUT_PARAM; + } + if( ! inVal ) { + cout << "msi_json_arrayops - invalid inVal" << endl; + return SYS_INVALID_INPUT_PARAM; + } + + string strOps(inOps); + + json_error_t error; + json_t *root; + + // try to make initial inJsonStr if it's an empty string + if ( strcmp(inJsonStr, "") == 0 ) { + inJsonStr = "[]"; + } + + // try to load JSON object + root = json_loads(inJsonStr, 0, &error); + if ( ! root || ! json_is_array(root) ) { + cout << "msi_json_arrayops - invalid json document" << endl; + json_decref(root); + return SYS_INVALID_INPUT_PARAM; + } + + int outSizeOrIndex = (int) json_array_size(root); + json_t *jval; + if (strcmp(inVal, "null") == 0) { + jval = json_null(); + } else if (strcmp(inVal, "true") == 0 ) { + jval = json_true(); + } else if (strcmp(inVal, "false") == 0 ) { + jval = json_false(); + } else { + jval = json_loads(inVal, 0, &error); + if ( ! jval ) jval = json_string(inVal); + } + + // find if jval is already presented in array + size_t i_match = outSizeOrIndex; + for( int i=0; i < outSizeOrIndex; i++ ) { + json_t *ov = json_array_get(root, i); + if ( json_equal(ov, jval) ) { i_match = i; break; } + } + + if ( strOps == "add" ) { + // append value only if it's a boolean, or it's not presented in the array + if ( json_is_boolean(jval) || i_match == outSizeOrIndex ) { + json_array_append_new(root, jval); + outSizeOrIndex = (int) json_array_size(root); + } + } else if ( strOps == "find" || strOps == "rm" ) { + + if ( i_match < outSizeOrIndex ) { + if ( strOps == "rm" ) { + json_array_remove(root, i_match); + outSizeOrIndex = (int) json_array_size(root); + } else { + outSizeOrIndex = i_match; + } + } else if ( strOps == "find" ) { + outSizeOrIndex = -1; + } + } else if ( strOps == "size" ) { + outSizeOrIndex = (int) json_array_size(root); + } else if ( strOps == "get" ) { + json_t *elem = json_array_get(root, inIdx); + + /* output a string directly, but encode other json types using json_dumps with JSON_ENCODE_ANY set */ + if ( json_is_string(elem)) { + fillStrInMsParam(val, json_string_value(elem)); + } else { + fillStrInMsParam(val, json_dumps(elem, JSON_ENCODE_ANY)); + } + + + outSizeOrIndex = inIdx; + } + + fillStrInMsParam(json_str, json_dumps(root, 0)); + fillIntInMsParam(sizeOrIndex, outSizeOrIndex); + + json_decref(root); + + // Done + return 0; + } + + irods::ms_table_entry* plugin_factory() { + irods::ms_table_entry* msvc = new irods::ms_table_entry(4); + + msvc->add_operation("msi_json_arrayops_impl", "msi_json_arrayops"); + + return msvc; + } + +} // extern "C" diff --git a/src/msi_json_objops.cc b/src/msi_json_objops.cc new file mode 100644 index 0000000000000000000000000000000000000000..1ec7540bc4e05ca66882bd5f5cce1de96fc9a9ab --- /dev/null +++ b/src/msi_json_objops.cc @@ -0,0 +1,140 @@ +// =-=-=-=-=-=-=- +#include "apiHeaderAll.hpp" +#include "msParam.hpp" +#include "reGlobalsExtern.hpp" +#include "irods_ms_plugin.hpp" +#include "reFuncDefs.hpp" +#include "jansson.h" + +// =-=-=-=-=-=-=- +// STL/boost Includes +#include <string> +#include <iostream> +#include <vector> +#include <boost/algorithm/string.hpp> + +extern "C" { + // =-=-=-=-=-=-=- + int msi_json_objops_impl(msParam_t* json_str, msParam_t* kvp, msParam_t* ops, ruleExecInfo_t* rei) { + using std::cout; + using std::endl; + using std::string; + + char *null_cstr = new char[5]; + char *true_cstr = new char[5]; + char *false_cstr = new char[6]; + + strcpy(null_cstr, "null"); + strcpy(true_cstr, "true"); + strcpy(false_cstr, "false"); + + // input type check + const char *inJsonStr = parseMspForStr( json_str ); + const char *inOps = parseMspForStr( ops ); + if( ! inJsonStr || ! inOps) { + cout << "msi_json_objops - invalid input for string" << endl; + return SYS_INVALID_INPUT_PARAM; + } + + if (kvp == NULL || kvp->inOutStruct == NULL || + kvp->type == NULL || strcmp(kvp->type, KeyValPair_MS_T) != 0) { + cout << "msi_json_objops - invalid input for key-value pairs" << endl; + return SYS_INVALID_INPUT_PARAM; + } + + keyValPair_t *inKVP; + inKVP = (keyValPair_t*) kvp->inOutStruct; + + string strOps(inOps); + + json_error_t error; + json_t *root; + + // try to make initial inJsonStr if it's an empty string + if ( strcmp(inJsonStr, "") == 0 ) { + inJsonStr = "{}"; + } + + // try to load JSON object + root = json_loads(inJsonStr, 0, &error); + if ( ! root ) { + cout << "msi_json_objops - invalid json document" << endl; + json_decref(root); + return SYS_INVALID_INPUT_PARAM; + } + + for (int ik=0; ik<inKVP->len; ik++) { + + char* inKey = inKVP->keyWord[ik]; + char* inVal = inKVP->value[ik]; + + json_t *jval; + if (strcmp(inVal, null_cstr) == 0) { + jval = json_null(); + } else { + jval = json_loads(inVal, 0, &error); + if ( ! jval ) jval = json_string(inVal); + } + + // try to find objects in the key + json_t *data = json_object_get(root, inKey); + + if ( strOps == "get" ) { + if ( json_is_null(data) ) { + inKVP->value[ik] = null_cstr; + } else if ( json_is_true(data) ) { + inKVP->value[ik] = true_cstr; + } else if ( json_is_false(data) ) { + inKVP->value[ik] = false_cstr; + } else if ( json_is_string(data) ) { + const char* val_str = json_string_value(data); + char *val_cstr = new char[strlen(val_str) + 1]; + strcpy(val_cstr, val_str); + inKVP->value[ik] = val_cstr; + } else { + inKVP->value[ik] = json_dumps(data,0); + } + } else if ( strOps == "add" ) { + if (json_is_array( data )) { + json_array_append_new(data, jval); + } else { + json_object_set_new(root, inKey, jval); + } + } else if ( strOps == "set" ) { + json_object_set_new(root, inKey, jval); + } else if ( strOps == "rm" ) { + if ( data ) { + if ( json_is_array( data ) ) { + size_t i_match = json_array_size(data); + for( int i=0; i<json_array_size(data); i++ ) { + json_t *ov = json_array_get(data, i); + if ( json_equal(ov, jval) ) { i_match = i; break; } + } + if ( i_match < json_array_size(data) ) json_array_remove(data, i_match); + } else if ( json_equal(data, jval) ) { + json_object_del(root, inKey); + } + } + } + } + + kvp->inOutStruct = (void *) inKVP; + kvp->type = (char *) strdup(KeyValPair_MS_T); + + fillStrInMsParam(json_str, json_dumps(root, 0)); + + json_decref(root); + + // Done + return 0; + } + + irods::ms_table_entry* plugin_factory() { + irods::ms_table_entry* msvc = new irods::ms_table_entry(3); + + msvc->add_operation("msi_json_objops_impl", "msi_json_objops"); + + return msvc; + } + +} // extern "C" diff --git a/tests/msi_json_arrayops_test.r b/tests/msi_json_arrayops_test.r new file mode 100644 index 0000000000000000000000000000000000000000..5e41b1a188215f0301bd2a123bdfe72f7dcecbf9 --- /dev/null +++ b/tests/msi_json_arrayops_test.r @@ -0,0 +1,66 @@ +myTestRule { + + *json_str = '[]'; + + *item1 = 'test & and %'; + *item2 = 'U123456-ru.nl'; + *item3 = '{"type":"PMID","id":"12345"}'; + *item4 = '{"type":"arXiv","id":"xyz/abc"}'; + *item5 = 'null'; + *item6 = 'true'; + *item7 = 'false'; + *item8 = '["DICOM","test"]'; + + ## build JSON array + *size = 0; + *ec = errorcode( msi_json_arrayops(*json_str, *item1, "add", *size) ); + #*ec = errorcode( msi_json_arrayops(*json_str, *item2, "add", *size) ); + *ec = errorcode( msi_json_arrayops(*json_str, *item3, "add", *size) ); + #*ec = errorcode( msi_json_arrayops(*json_str, *item4, "add", *size) ); + #*ec = errorcode( msi_json_arrayops(*json_str, *item6, "add", *size) ); + #*ec = errorcode( msi_json_arrayops(*json_str, *item7, "add", *size) ); + *ec = errorcode( msi_json_arrayops(*json_str, *item8, "add", *size) ); + + # adding null does not supported? + #*ec = errorcode( msi_json_arrayops(*json_str, *item5, "add", *size) ); + + # adding those will append additional boolean values to array + #*ec = errorcode( msi_json_arrayops(*json_str, *item6, "add", *size) ); + #*ec = errorcode( msi_json_arrayops(*json_str, *item7, "add", *size) ); + + # adding those will not change array because they have been presented in it + #*ec = errorcode( msi_json_arrayops(*json_str, *item1, "add", *size) ); + #*ec = errorcode( msi_json_arrayops(*json_str, *item2, "add", *size) ); + #*ec = errorcode( msi_json_arrayops(*json_str, *item3, "add", *size) ); + #*ec = errorcode( msi_json_arrayops(*json_str, *item4, "add", *size) ); + + writeLine("stdout", *json_str ++ " size: " ++ *size); + + ## remove JSON object (rm/find) + #*ec = errorcode( msi_json_arrayops(*json_str, *item3, "rm", *size) ); + #writeLine("stdout", *json_str ++ " size: " ++ *size); + + #*idx = 0; + #*ec = errorcode( msi_json_arrayops(*json_str, *item5, "find", *idx) ); + #writeLine("stdout", *json_str ++ " " ++ *item5 ++ " at idx: " ++ str(*idx)); + + #*ec = errorcode( msi_json_arrayops(*json_str, *item4, "find", *idx) ); + #writeLine("stdout", *json_str ++ " " ++ *item4 ++ " at idx: " ++ str(*idx)); + + #*ec = errorcode( msi_json_arrayops(*json_str, *item1, "find", *idx) ); + #writeLine("stdout", *json_str ++ " " ++ *item1 ++ " at idx: " ++ str(*idx)); + + #*ec = errorcode( msi_json_arrayops(*json_str, "", "size", *idx) ); + #writeLine("stdout", *json_str ++ " size: " ++ str(*idx)); + + *mysize = 0; + *ec = errorcode( msi_json_arrayops(*json_str, "", "size", *mysize) ); + writeLine("stdout", *json_str ++ " size: " ++ str(*mysize)); + + *idx = 0; + *item1 = ""; + *ec = errorcode( msi_json_arrayops(*json_str, *item1, "get", *idx) ); + writeLine("stdout", *json_str ++ " " ++ *item1 ++ " at idx: " ++ str(*idx)); + +} +OUTPUT ruleExecOut diff --git a/tests/msi_json_objops_test.r b/tests/msi_json_objops_test.r new file mode 100644 index 0000000000000000000000000000000000000000..10697a586dc17075d2fc1652a51af5f40a207670 --- /dev/null +++ b/tests/msi_json_objops_test.r @@ -0,0 +1,42 @@ +myTestRule { + + *json_str = ""; + + ## build JSON object from key-value pairs + msiString2KeyValPair("", *kvp); + msiAddKeyVal(*kvp, 'type', 'DATA_SHARING'); + msiAddKeyVal(*kvp, 'collId', '12345'); + msiAddKeyVal(*kvp, 'title', 'DICOM 原生資料備份'); + msiAddKeyVal(*kvp, 'creatorList', '["U505173-ru.nl","hurngchunlee-icloud.com"]'); + msiAddKeyVal(*kvp, 'associatedPublication', '[{"type":"PMID", "id":"654321"}]'); + + *ec = errorcode( msi_json_objops(*json_str, *kvp, "add") ); + writeLine("stdout", *json_str); + + ## modify JSON object (add/set/get/rm) + msiString2KeyValPair("", *kvp); + msiAddKeyVal(*kvp, 'associatedPublication', '{"type":"arXiv", "id":"xyz/123"}'); + *ec = errorcode( msi_json_objops(*json_str, *kvp, "add") ); + writeLine("stdout", *json_str); + + msiString2KeyValPair("", *kvp); + msiAddKeyVal(*kvp, 'associatedPublication', '{"type":"PMID", "id":"654321"}'); + *ec = errorcode( msi_json_objops(*json_str, *kvp, "rm") ); + writeLine("stdout", *json_str); + + msiString2KeyValPair("", *kvp); + msiAddKeyVal(*kvp, 'creatorList', '["hurngchunlee-icloud.com","U505173-ru.nl"]'); + *ec = errorcode( msi_json_objops(*json_str, *kvp, "set") ); + writeLine("stdout", *json_str); + + msiString2KeyValPair("", *kvp); + msiAddKeyVal(*kvp, 'creatorList', ''); + msiAddKeyVal(*kvp, 'associatedPublication', ''); + msiAddKeyVal(*kvp, 'collId', ''); + msiAddKeyVal(*kvp, 'title', ''); + #msiAddKeyVal(*kvp, 'associatedPublication', ''); + *ec = errorcode( msi_json_objops(*json_str, *kvp, "get") ); + writeLine("stdout", str(*kvp)); +} +OUTPUT ruleExecOut +