/** * $Id: ehrclient.cpp 2 2009-10-31 02:48:23Z l0728348 $ * * Copyright 2009 * * @author Manuel Mausz (0728348) * @brief implements the main routines of the console application */ #include #include #include #include #include #include #include "ehrclient.h" #include "getoptwrapper.h" #include "cairodocument.h" #include "utils.h" using namespace std; using namespace Ehr; int EhrClient::run(int argc, char *argv[]) { /* setup commandline option */ CommandOption optnode("node", 0, "The object identifier string used to connect to the EHR provider node", CommandOption::needArg); CommandOption optreq("requestor", 0, "The PEM file containing the requestors certifcate and private key for signing", CommandOption::needArg); CommandOption optowner("owner", 0, "The PEM file containing the patients certifcate and private key for signing", CommandOption::needArg); CommandOption optlist("list", 0, "Display a list of all documents available in the patient's EHR", CommandOption::noArg); CommandOption optdoc("document", 0, "The ID of the document selected for exporting to PDF", CommandOption::needArg); CommandOption optout("output", 0, "The output file", CommandOption::needArg); CommandOption opthelp("help", 0, "Display this help and exit", CommandOption::noArg); CommandOptionParse opts(argc, argv, "Allowed options"); opts.addSynopsis("--node --requestor --owner --list"); opts.addSynopsis("--node --requestor --owner --document --output "); /* error during parsing or help-option set */ if (!opts.parse() || opts["help"]) { cerr << opts; return EXIT_FAILURE; } /* check commandline option combinations */ try { /* missing mandatory options */ if (!opts["node"] || !opts["requestor"] || !opts["owner"] || (!opts["list"] && !opts["document"])) throw runtime_error("missing mandatory commandline options"); /* list-option needs to be alone (and vica-versa) */ if (opts["list"] && (opts["document"] || opts["output"])) throw runtime_error("unable to combine these commandline options"); /* document-option needs output-option */ if (opts["document"] && !opts["output"]) throw runtime_error("--document needs --output"); /* don't allow options to occur multiple times */ if (opts["node"] > 1 || opts["requestor"] > 1 || opts["owner"] > 1 || opts["list"] > 1 || opts["document"] > 1 || opts["output"] > 1) throw runtime_error("commandline options may occur only once"); } catch(runtime_error& ex) { cerr << appName() << ": " << ex.what() << endl << opts; return EXIT_FAILURE; } /* save for later */ m_ownercert = opts["owner"].value(); m_requestorcert = opts["requestor"].value(); /* set some properties */ Ice::PropertiesPtr props = communicator()->getProperties(); if (props->getProperty("IceSSL.CertAuthFile").empty()) props->setProperty("IceSSL.CertAuthFile", "./certs/rootca.pem"); props->setProperty("IceSSL.CertFile", m_requestorcert); /* load plugins */ Ice::PluginManagerPtr pluginmgr = communicator()->getPluginManager(); pluginmgr->initializePlugins(); /* get security instance */ m_security = &Security::instance(); /* get provider proxy */ Ice::ObjectPrx base = communicator()->stringToProxy(opts["node"]); if (!(m_provider = ProviderPrx::checkedCast(base))) throw runtime_error("error - invalid node or doesn't exist"); try { if (opts["list"]) listDocuments(); else if (opts["document"] && opts["output"]) { long docid; try { docid = Utils::lexical_cast(opts["document"].value()); } catch(Utils::bad_lexical_cast& ex) { throw runtime_error("error - invalid document-parameter - not a number"); } if (docid <= 0) throw runtime_error("error - invalid document-parameter - only positive numbers"); saveDocument(docid, opts["output"]); } } catch(EhrException& e) { throw runtime_error("error - " + e.what); } return 0; } /*----------------------------------------------------------------------------*/ #include Ehr::DocumentList EhrClient::getDocumentList() { /* fill in request */ ListDocumentsRequestPtr request = new ListDocumentsRequest(); pair cnowner = splitCN(m_ownercert); pair cnrequestor = splitCN(m_requestorcert); request->owner.user = cnowner.first; request->owner.provider = cnowner.second; request->requestor.user = cnrequestor.first; request->requestor.provider = cnrequestor.second; request->when.msecs = getTime(); /* objects containing the signature */ Signature requestor; Signature owner; /* sign the request */ vector data = convertToByteStream(request); m_security->sign(m_requestorcert, data, requestor.data); m_security->sign(m_ownercert, data, owner.data); /* execute */ return m_provider->listDocuments(request, requestor, owner); } /*----------------------------------------------------------------------------*/ Ehr::DocumentList EhrClient::searchDocument(const bool hasFrom, const long from, const bool hasTill, const long till, const Ehr::DocumentType doctype, const long docid) { /* fill in request */ FindDocumentsRequestPtr request = new FindDocumentsRequest(); pair cnowner = splitCN(m_ownercert); pair cnrequestor = splitCN(m_requestorcert); request->owner.user = cnowner.first; request->owner.provider = cnowner.second; request->requestor.user = cnrequestor.first; request->requestor.provider = cnrequestor.second; request->when.msecs = getTime(); request->hasFrom = hasFrom; request->from.msecs = from; request->hasTill = hasTill; request->from.msecs = till; request->type = doctype; request->documentId = docid; /* objects containing the signature */ Signature requestor; Signature owner; /* sign the request */ vector data = convertToByteStream(request); m_security->sign(m_requestorcert, data, requestor.data); m_security->sign(m_ownercert, data, owner.data); /* execute */ return m_provider->findDocuments(request, requestor, owner); } /*----------------------------------------------------------------------------*/ void EhrClient::listDocuments() { /* get document list */ DocumentList doclist = getDocumentList(); DocumentPtr doc = new Document(); /* print the list */ cout << "Documentlist for " << m_security->getCommonName(m_ownercert) << ":" << endl; if (doclist.size() != 0) { for(unsigned i = 0; i < doclist.size(); ++i) { /* decrypt our document */ vector data; m_security->decryptPrivate(m_ownercert, doclist[i].document.encryptedData, doclist[i].document.initialVector, doclist[i].document.encryptedKey, data); Ice::InputStreamPtr in = Ice::createInputStream(communicator(), data); ice_readDocument(in, doc); in->readPendingObjects(); cout << " ID#" << doclist[i].id << " " << getDate(doclist[i].created.msecs) << endl; if (doc->type == DOCPRESCRIPTION || doc->type == DOCCONSUMEDPRESCRIPTION) { PrescriptionPtr presc = PrescriptionPtr::dynamicCast(doc); cout << " Orginator: " << presc->originatorName << ", " << presc->originatorProfession << endl << " Parmaceutical: " << presc->drugDescription << endl << " Valid: " << getDate(presc->validFrom) << " - " << getDate(presc->expires) << endl; } cout << endl; } } else cout << "There are no documents available" << endl; } /*----------------------------------------------------------------------------*/ void EhrClient::saveDocument(const long docid, const std::string& output) { /* first check if file already exist */ ifstream ifile(output.c_str()); if (ifile) throw runtime_error("error - output file already exist"); ifile.close(); /* search for document */ DocumentList doclist = searchDocument(docid); if (doclist.size() == 0) throw runtime_error("error - document not found"); else if (doclist.size() > 1) throw runtime_error("error - document exists multiple times"); /* decrypt our document */ DocumentPtr doc = new Document(); vector data; m_security->decryptPrivate(m_ownercert, doclist[0].document.encryptedData, doclist[0].document.initialVector, doclist[0].document.encryptedKey, data); Ice::InputStreamPtr in = Ice::createInputStream(communicator(), data); ice_readDocument(in, doc); in->readPendingObjects(); if (doc->type == DOCPRESCRIPTION || doc->type == DOCCONSUMEDPRESCRIPTION) { PrescriptionPtr presc = PrescriptionPtr::dynamicCast(doc); /* create our pdf document */ //CairoDocument cairodoc(210, 148); // A6 lanscape CairoDocument cairodoc(148, 105); // A5 landscape CairoColor colblack(0, 0, 0); CairoColor colgrey(0.5, 0.5, 0.5); CairoFont fontsmall(7, "sans-serif"); CairoFont fontbig(10, "sans-serif"); /* set default */ cairodoc.add(fontbig).add(colblack); /* our current position */ double docleft = 0; double doctop = 0; double docwidth = cairodoc.width(); double docheight = cairodoc.height(); double padding = 3; /* add document border */ CairoRectangle docborder(docleft += padding, doctop += padding, docwidth -= 2 * padding, docheight -= 2 * padding, 0.2); cairodoc.add(docborder); /* add inner padding */ docleft += padding; doctop += padding; docwidth -= 2 * padding; docheight -= 2 * padding; /* add consumer name + label */ double boxleftwith = (docwidth - docleft) * 3 / 4; double boxleftwithin = boxleftwith - padding; CairoTextBox consumerlabel(docleft, doctop, boxleftwithin, fontsmall.height()); consumerlabel << "Consumer name:"; cairodoc.add(consumerlabel.add(colgrey).add(fontsmall)); doctop += consumerlabel.height(); CairoTextBox consumername(docleft, doctop, boxleftwithin, fontbig.height()); consumername << presc->consumerName; cairodoc.add(consumername); doctop += consumername.height(); /* add consumer birth + label */ doctop += 1; CairoTextBox consumerdatelabel(docleft, doctop, boxleftwithin, fontsmall.height(), 0); consumerdatelabel << "Date of birth:"; cairodoc.add(consumerdatelabel.add(colgrey).add(fontsmall)); doctop += consumerdatelabel.height(); CairoTextBox consumerdate(docleft, doctop, boxleftwithin, fontbig.height()); consumerdate << getDate(presc->consumerDateOfBirth); cairodoc.add(consumerdate); doctop += consumerdate.height(); /* put that in a box */ CairoRectangle consumerborder(docborder.x(), docborder.y(), docleft - docborder.x() + boxleftwith, doctop - docborder.y() + padding); cairodoc.add(consumerborder); doctop += 2 * padding; /* add originator */ CairoTextBox originatorlabel(docleft, doctop, boxleftwithin, fontsmall.height()); originatorlabel << "Originator:"; cairodoc.add(originatorlabel.add(colgrey).add(fontsmall)); doctop += originatorlabel.height(); CairoTextBox originator(docleft, doctop, boxleftwithin, fontbig.height() * 2); originator << presc->originatorName << ", " << presc->originatorProfession << "\n" << presc->originatorAddress; cairodoc.add(originator); doctop += originator.height(); /* put that in a box */ CairoRectangle originatorborder(docborder.x(), docborder.y(), docleft - docborder.x() + boxleftwith, originator.y() + originator.height()); cairodoc.add(originatorborder); doctop += 2 * padding; /* add left box */ double boxrightx = consumerlabel.x() + boxleftwith; double boxrighty = docborder.y(); double boxrightwidth = docborder.x() + docborder.width() - boxrightx; CairoRectangle datesborder(boxrightx, boxrighty, boxrightwidth, originator.y() + originator.height()); cairodoc.add(datesborder); /* add inner padding */ double boxrightxin = boxrightx + padding; boxrighty += padding; double boxrightwidthin = boxrightwidth - 2 * padding; /* add creation date */ CairoTextBox createdlabel(boxrightxin, boxrighty, boxrightwidthin, fontsmall.height()); createdlabel << "Created on:"; cairodoc.add(createdlabel.add(colgrey).add(fontsmall)); boxrighty += consumerlabel.height(); CairoTextBox created(boxrightxin, boxrighty, boxrightwidthin, fontbig.height()); created << getDate(presc->creationDate); cairodoc.add(created); boxrighty += created.height(); boxrighty += 1; /* add valid from date */ CairoTextBox validfromlabel(boxrightxin, boxrighty, boxrightwidthin, fontsmall.height()); validfromlabel << "Valid from:"; cairodoc.add(validfromlabel.add(colgrey).add(fontsmall)); boxrighty += validfromlabel.height(); CairoTextBox valid(boxrightxin, boxrighty, boxrightwidthin, fontbig.height()); valid << getDate(presc->validFrom); cairodoc.add(valid); boxrighty += valid.height(); boxrighty += 1; /* add expire date */ CairoTextBox expirelabel(boxrightxin, boxrighty, boxrightwidthin, fontsmall.height()); expirelabel << "Expire on:"; cairodoc.add(expirelabel.add(colgrey).add(fontsmall)); boxrighty += expirelabel.height(); CairoTextBox expire(boxrightxin, boxrighty, boxrightwidthin, fontbig.height()); expire << getDate(presc->expires); cairodoc.add(expire); /* add drug */ CairoTextBox druglabel(docleft, doctop, docwidth, fontsmall.height()); druglabel << "Pharmaceutical:"; cairodoc.add(druglabel.add(colgrey).add(fontsmall)); doctop += druglabel.height(); CairoTextBox drug(docleft, doctop, docwidth, fontbig.height()); drug << presc->drugDescription << "\n" << presc->dosage << " " << presc->measuringUnit << ", " << presc->form; cairodoc.add(drug); if (doc->type == DOCCONSUMEDPRESCRIPTION) { ConsumedPrescriptionPtr presc2 = ConsumedPrescriptionPtr::dynamicCast(doc); /* add consumed prescreption */ double boxbottomy = docborder.y() + docborder.height() - (2 * padding + 2 * fontbig.height() + fontsmall.height()); CairoRectangle consumedborder(docborder.x(), boxbottomy, docleft - docborder.x() + boxleftwith, docborder.height() - boxbottomy + docborder.x()); cairodoc.add(consumedborder); double boxbottomyin = boxbottomy + padding; CairoTextBox consumedlabel(docleft, boxbottomyin, boxleftwithin, fontsmall.height()); consumedlabel << "Consumed by:"; cairodoc.add(consumedlabel.add(colgrey).add(fontsmall)); CairoTextBox consumeddatelabel(boxrightxin, boxbottomyin, boxrightwidth, fontsmall.height()); consumeddatelabel << "Consumed on:"; cairodoc.add(consumeddatelabel.add(colgrey).add(fontsmall)); boxbottomyin += consumedlabel.height(); CairoTextBox consumed(docleft, boxbottomyin, boxleftwithin, fontbig.height() * 2); consumed << presc2->dispenserName << ", " << presc2->dispenserProfession << "\n" << presc2->dispenserAddress; cairodoc.add(consumed); CairoTextBox consumedon(boxrightxin, boxbottomyin, boxrightwidth, fontbig.height()); consumedon << getDate(presc2->dispensingDate); cairodoc.add(consumedon); /* add consumed date border */ CairoRectangle consumedborder2(boxrightx, boxbottomy, boxrightwidth, docborder.height() - boxbottomy + docborder.x()); cairodoc.add(consumedborder2); } cairodoc.save(output, CairoDocument::PDF); } else throw runtime_error("error - unsupported document type"); return; } /*----------------------------------------------------------------------------*/ long EhrClient::getTime() { /* get time in milliseconds */ struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec * 1000 + tv.tv_usec / 1000; } /*----------------------------------------------------------------------------*/ std::string EhrClient::getDate(long msecs) { struct tm doctime; const time_t timep = msecs / 1000; localtime_r(&timep, &doctime); stringstream out(""); out << doctime.tm_mday << "." << doctime.tm_mon << "." << doctime.tm_year + 1900 << " " << doctime.tm_hour << ':' << doctime.tm_min << ':' << doctime.tm_sec; return out.str(); } /*----------------------------------------------------------------------------*/ std::string EhrClient::getDate(const Ehr::Date& date) { stringstream out(""); out << static_cast(date.day) << "." << static_cast(date.month) << "." << date.year; return out.str(); } /*----------------------------------------------------------------------------*/ vector EhrClient::convertToByteStream(const Ice::ObjectPtr& request) { /* in order to sign our request, we need to convert * the request to a bytestream first */ Ice::OutputStreamPtr out = Ice::createOutputStream(communicator()); vector data; out->writeObject(request); out->writePendingObjects(); out->finished(data); return data; } /*----------------------------------------------------------------------------*/ std::pair EhrClient::splitCN(const std::string& cert) { /* parse requestor details from cert */ string commonname = m_security->getCommonName(cert); size_t pos = commonname.find_first_of('@'); if (pos == string::npos) throw runtime_error("error - commonname of cert \"" + cert + "\" doesn't have user@provider syntax"); return pair(commonname.substr(0, pos), commonname.substr(pos + 1)); } /* vim: set et sw=2 ts=2: */