//////////////////////////////////////////////////////////////////////////////// /// @brief abstract base request handler /// /// @file /// /// DISCLAIMER /// /// Copyright 2004-2012 triagens GmbH, Cologne, Germany /// /// 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. /// /// Copyright holder is triAGENS GmbH, Cologne, Germany /// /// @author Dr. Frank Celler /// @author Copyright 2010-2012, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// #include "RestVocbaseBaseHandler.h" #include "Basics/StringUtils.h" #include "BasicsC/conversions.h" #include "BasicsC/string-buffer.h" #include "BasicsC/strings.h" #include "Rest/HttpRequest.h" #include "ResultGenerator/OutputGenerator.h" #include "ShapedJson/shaped-json.h" #include "Variant/VariantArray.h" #include "Variant/VariantBoolean.h" #include "Variant/VariantInt32.h" #include "Variant/VariantString.h" #include "Variant/VariantUInt64.h" #include "VocBase/document-collection.h" using namespace std; using namespace triagens::basics; using namespace triagens::rest; using namespace triagens::arango; // ----------------------------------------------------------------------------- // --SECTION-- REST_VOCBASE_BASE_HANDLER // ----------------------------------------------------------------------------- // ----------------------------------------------------------------------------- // --SECTION-- public constants // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @addtogroup ArangoDB /// @{ //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief result RES-OK //////////////////////////////////////////////////////////////////////////////// LoggerData::Extra const RestVocbaseBaseHandler::RES_OK; //////////////////////////////////////////////////////////////////////////////// /// @brief result RES-ERR //////////////////////////////////////////////////////////////////////////////// LoggerData::Extra const RestVocbaseBaseHandler::RES_ERR; //////////////////////////////////////////////////////////////////////////////// /// @brief result RES-ERR //////////////////////////////////////////////////////////////////////////////// LoggerData::Extra const RestVocbaseBaseHandler::RES_FAIL; //////////////////////////////////////////////////////////////////////////////// /// @brief document path //////////////////////////////////////////////////////////////////////////////// string RestVocbaseBaseHandler::DOCUMENT_PATH = "/_api/document"; //////////////////////////////////////////////////////////////////////////////// /// @brief document path //////////////////////////////////////////////////////////////////////////////// string RestVocbaseBaseHandler::EDGE_PATH = "/_api/edge"; //////////////////////////////////////////////////////////////////////////////// /// @brief collection path //////////////////////////////////////////////////////////////////////////////// string RestVocbaseBaseHandler::COLLECTION_PATH = "/_api/collection"; //////////////////////////////////////////////////////////////////////////////// /// @brief documents import path //////////////////////////////////////////////////////////////////////////////// string RestVocbaseBaseHandler::DOCUMENT_IMPORT_PATH = "/_api/import"; //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // --SECTION-- constructors and destructors // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @addtogroup ArangoDB /// @{ //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief constructor //////////////////////////////////////////////////////////////////////////////// RestVocbaseBaseHandler::RestVocbaseBaseHandler (HttpRequest* request, TRI_vocbase_t* vocbase) : RestBaseHandler(request), _vocbase(vocbase), _collection(0), _documentCollection(0), _barrier(0), _timing(), _timingResult(RES_FAIL) { } //////////////////////////////////////////////////////////////////////////////// /// @brief destructor //////////////////////////////////////////////////////////////////////////////// RestVocbaseBaseHandler::~RestVocbaseBaseHandler () { if (_barrier != 0) { TRI_FreeBarrier(_barrier); } LOGGER_REQUEST_IN_END_I(_timing) << _timingResult; } //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // --SECTION-- protected methods // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @addtogroup ArangoDB /// @{ //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief generates ok message without content //////////////////////////////////////////////////////////////////////////////// void RestVocbaseBaseHandler::generateOk () { response = new HttpResponse(HttpResponse::NO_CONTENT); } //////////////////////////////////////////////////////////////////////////////// /// @brief generates created message //////////////////////////////////////////////////////////////////////////////// void RestVocbaseBaseHandler::generateCreated (TRI_voc_cid_t cid, TRI_voc_did_t did, TRI_voc_rid_t rid) { string cidStr = StringUtils::itoa(cid); string didStr = StringUtils::itoa(did); string ridStr = StringUtils::itoa(rid); string handle = cidStr + "/" + didStr; response = new HttpResponse(HttpResponse::CREATED); response->setContentType("application/json; charset=utf-8"); response->setHeader("ETag", "\"" + ridStr + "\""); response->setHeader("location", DOCUMENT_PATH + "/" + handle); response->body() .appendText("{\"error\":false,\"_id\":\"") .appendText(handle.c_str()) .appendText("\",\"_rev\":") .appendInteger(rid) .appendText("}"); } //////////////////////////////////////////////////////////////////////////////// /// @brief generates accepted message //////////////////////////////////////////////////////////////////////////////// void RestVocbaseBaseHandler::generateAccepted (TRI_voc_cid_t cid, TRI_voc_did_t did, TRI_voc_rid_t rid) { string cidStr = StringUtils::itoa(cid); string didStr = StringUtils::itoa(did); string ridStr = StringUtils::itoa(rid); string handle = cidStr + "/" + didStr; response = new HttpResponse(HttpResponse::ACCEPTED); response->setContentType("application/json; charset=utf-8"); response->setHeader("ETag", "\"" + ridStr + "\""); response->setHeader("location", DOCUMENT_PATH + "/" + handle); response->body() .appendText("{\"error\":false,\"_id\":\"") .appendText(handle.c_str()) .appendText("\",\"_rev\":") .appendInteger(rid) .appendText("}"); } //////////////////////////////////////////////////////////////////////////////// /// @brief generates deleted message //////////////////////////////////////////////////////////////////////////////// void RestVocbaseBaseHandler::generateDeleted (TRI_voc_cid_t cid, TRI_voc_did_t did, TRI_voc_rid_t rid) { string cidStr = StringUtils::itoa(cid); string didStr = StringUtils::itoa(did); string ridStr = StringUtils::itoa(rid); string handle = cidStr + "/" + didStr; response = new HttpResponse(HttpResponse::OK); response->setContentType("application/json; charset=utf-8"); response->body() .appendText("{\"error\":false,\"_id\":\"") .appendText(handle.c_str()) .appendText("\",\"_rev\":") .appendInteger(rid) .appendText("}"); } //////////////////////////////////////////////////////////////////////////////// /// @brief generates updated message //////////////////////////////////////////////////////////////////////////////// void RestVocbaseBaseHandler::generateUpdated (TRI_voc_cid_t cid, TRI_voc_did_t did, TRI_voc_rid_t rid) { string cidStr = StringUtils::itoa(cid); string didStr = StringUtils::itoa(did); string ridStr = StringUtils::itoa(rid); string handle = cidStr + "/" + didStr; response = new HttpResponse(HttpResponse::OK); response->setContentType("application/json; charset=utf-8"); response->body() .appendText("{\"error\":false,\"_id\":\"") .appendText(handle.c_str()) .appendText("\",\"_rev\":") .appendInteger(rid) .appendText("}"); } //////////////////////////////////////////////////////////////////////////////// /// @brief generates collection not found error message //////////////////////////////////////////////////////////////////////////////// void RestVocbaseBaseHandler::generateCollectionNotFound (string const& cid) { generateError(HttpResponse::NOT_FOUND, TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND, "collection " + COLLECTION_PATH + "/" + cid + " not found"); } //////////////////////////////////////////////////////////////////////////////// /// @brief generates document not found error message //////////////////////////////////////////////////////////////////////////////// void RestVocbaseBaseHandler::generateDocumentNotFound (TRI_voc_cid_t cid, string const& did) { string location = DOCUMENT_PATH + "/" + StringUtils::itoa(cid) + TRI_DOCUMENT_HANDLE_SEPARATOR_STR + did; generateError(HttpResponse::NOT_FOUND, TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND, "document " + location + " not found"); } //////////////////////////////////////////////////////////////////////////////// /// @brief generates conflict message //////////////////////////////////////////////////////////////////////////////// void RestVocbaseBaseHandler::generateConflict (string const& cid, string const& did) { generateError(HttpResponse::CONFLICT, TRI_ERROR_ARANGO_CONFLICT, "document " + DOCUMENT_PATH + "/" + cid + "/" + did + " has been altered"); } //////////////////////////////////////////////////////////////////////////////// /// @brief generates not implemented //////////////////////////////////////////////////////////////////////////////// void RestVocbaseBaseHandler::generateNotImplemented (string const& path) { generateError(HttpResponse::NOT_IMPLEMENTED, TRI_ERROR_NOT_IMPLEMENTED, "'" + path + "' not implemented"); } //////////////////////////////////////////////////////////////////////////////// /// @brief generates forbidden //////////////////////////////////////////////////////////////////////////////// void RestVocbaseBaseHandler::generateForbidden () { generateError(HttpResponse::FORBIDDEN, TRI_ERROR_FORBIDDEN, "operation forbidden"); } //////////////////////////////////////////////////////////////////////////////// /// @brief generates precondition failed //////////////////////////////////////////////////////////////////////////////// void RestVocbaseBaseHandler::generatePreconditionFailed (TRI_voc_cid_t cid, TRI_voc_did_t did, TRI_voc_rid_t rid) { response = new HttpResponse(HttpResponse::PRECONDITION_FAILED); VariantArray* result = new VariantArray(); result->add("error", new VariantBoolean(true)); result->add("code", new VariantInt32((int32_t) HttpResponse::PRECONDITION_FAILED)); result->add("errorNum", new VariantInt32((int32_t) TRI_ERROR_ARANGO_CONFLICT)); result->add("errorMessage", new VariantString("precondition failed")); result->add("_id", new VariantString(StringUtils::itoa(cid) + TRI_DOCUMENT_HANDLE_SEPARATOR_STR + StringUtils::itoa(did))); result->add("_rev", new VariantUInt64(rid)); string contentType; bool ok = OutputGenerator::output(selectResultGenerator(request), response->body(), result, contentType); if (ok) { response->setContentType(contentType); } else { delete response; generateError(HttpResponse::SERVER_ERROR, TRI_ERROR_INTERNAL, "cannot generate response"); } delete result; } //////////////////////////////////////////////////////////////////////////////// /// @brief generates not modified //////////////////////////////////////////////////////////////////////////////// void RestVocbaseBaseHandler::generateNotModified (string const& etag) { response = new HttpResponse(HttpResponse::NOT_MODIFIED); response->setHeader("ETag", "\"" + etag + "\""); } //////////////////////////////////////////////////////////////////////////////// /// @brief generates next entry from a result set //////////////////////////////////////////////////////////////////////////////// void RestVocbaseBaseHandler::generateDocument (TRI_doc_mptr_t const* document, bool generateDocument) { if (document == 0 || _documentCollection == 0) { generateError(HttpResponse::SERVER_ERROR, TRI_ERROR_INTERNAL, "document or collection pinter is null, should not happen"); return; } // add document identifier to buffer TRI_string_buffer_t buffer; string id = StringUtils::itoa(_documentCollection->base._cid) + TRI_DOCUMENT_HANDLE_SEPARATOR_STR + StringUtils::itoa(document->_did); TRI_json_t augmented; TRI_InitArrayJson(TRI_UNKNOWN_MEM_ZONE, &augmented); TRI_json_t* _id = TRI_CreateStringCopyJson(TRI_UNKNOWN_MEM_ZONE, id.c_str()); if (_id) { TRI_Insert2ArrayJson(TRI_UNKNOWN_MEM_ZONE, &augmented, "_id", _id); } TRI_json_t* _rev = TRI_CreateNumberJson(TRI_UNKNOWN_MEM_ZONE, document->_rid); if (_rev) { TRI_Insert2ArrayJson(TRI_UNKNOWN_MEM_ZONE, &augmented, "_rev", _rev); } TRI_df_marker_type_t type = ((TRI_df_marker_t*) document->_data)->_type; if (type == TRI_DOC_MARKER_EDGE) { TRI_doc_edge_marker_t* marker = (TRI_doc_edge_marker_t*) document->_data; string from = StringUtils::itoa(marker->_fromCid) + TRI_DOCUMENT_HANDLE_SEPARATOR_STR + StringUtils::itoa(marker->_fromDid); string to = StringUtils::itoa(marker->_toCid) + TRI_DOCUMENT_HANDLE_SEPARATOR_STR + StringUtils::itoa(marker->_toDid); TRI_Insert3ArrayJson(TRI_UNKNOWN_MEM_ZONE, &augmented, "_from", TRI_CreateStringCopyJson(TRI_UNKNOWN_MEM_ZONE, from.c_str())); TRI_Insert3ArrayJson(TRI_UNKNOWN_MEM_ZONE, &augmented, "_to", TRI_CreateStringCopyJson(TRI_UNKNOWN_MEM_ZONE, to.c_str())); } // convert object to string TRI_InitStringBuffer(&buffer, TRI_UNKNOWN_MEM_ZONE); TRI_StringifyAugmentedShapedJson(_documentCollection->_shaper, &buffer, &document->_document, &augmented); TRI_DestroyJson(TRI_UNKNOWN_MEM_ZONE, &augmented); if (_id) { TRI_Free(TRI_UNKNOWN_MEM_ZONE, _id); } if (_rev) { TRI_Free(TRI_UNKNOWN_MEM_ZONE, _rev); } // and generate a response response = new HttpResponse(HttpResponse::OK); response->setContentType("application/json; charset=utf-8"); response->setHeader("ETag", "\"" + StringUtils::itoa(document->_rid) + "\""); if (generateDocument) { response->body().appendText(TRI_BeginStringBuffer(&buffer), TRI_LengthStringBuffer(&buffer)); } else { response->headResponse(TRI_LengthStringBuffer(&buffer)); } TRI_AnnihilateStringBuffer(&buffer); } //////////////////////////////////////////////////////////////////////////////// /// @brief extracts the revision //////////////////////////////////////////////////////////////////////////////// TRI_voc_rid_t RestVocbaseBaseHandler::extractRevision (string const& header, string const& parameter) { bool found; string etag = StringUtils::trim(request->header(header, found)); if (found && ! etag.empty() && etag[0] == '"' && etag[etag.length()-1] == '"') { return StringUtils::uint64(etag.c_str() + 1, etag.length() - 2); } else if (found) { return 0; } if (parameter.empty()) { return 0; } else { etag = request->value(parameter, found); if (found) { return StringUtils::uint64(etag.c_str()); } else { return 0; } } } //////////////////////////////////////////////////////////////////////////////// /// @brief extracts the update policy //////////////////////////////////////////////////////////////////////////////// TRI_doc_update_policy_e RestVocbaseBaseHandler::extractUpdatePolicy () { bool found; string policy = request->value("policy", found); if (found) { policy = StringUtils::tolower(policy); if (policy == "error") { return TRI_DOC_UPDATE_ERROR; } else if (policy == "last") { return TRI_DOC_UPDATE_LAST_WRITE; } else { return TRI_DOC_UPDATE_ILLEGAL; } } else { return TRI_DOC_UPDATE_ERROR; } } //////////////////////////////////////////////////////////////////////////////// /// @brief uses a collection, loading or manifesting and locking it //////////////////////////////////////////////////////////////////////////////// int RestVocbaseBaseHandler::useCollection (string const& name, bool create) { _collection = 0; _documentCollection = 0; // sanity check if (name.empty()) { generateError(HttpResponse::BAD, TRI_ERROR_HTTP_CORRUPTED_JSON, "collection identifier is empty"); return TRI_set_errno(TRI_ERROR_HTTP_CORRUPTED_JSON); } // try to find the collection if (isdigit(name[0])) { TRI_voc_cid_t id = StringUtils::uint64(name); _collection = TRI_LookupCollectionByIdVocBase(_vocbase, id); } else { _collection = TRI_FindCollectionByNameVocBase(_vocbase, name.c_str(), create); } if (_collection == 0) { generateCollectionNotFound(name); return TRI_set_errno(TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND); } // and use the collection int res = TRI_UseCollectionVocBase(_vocbase, const_cast(_collection)); if (res == TRI_ERROR_NO_ERROR) { _documentCollection = _collection->_collection; assert(_documentCollection != 0); } return res; } //////////////////////////////////////////////////////////////////////////////// /// @brief releases a collection //////////////////////////////////////////////////////////////////////////////// void RestVocbaseBaseHandler::releaseCollection () { if (_collection == 0) { return; } TRI_ReleaseCollectionVocBase(_vocbase, _collection); } //////////////////////////////////////////////////////////////////////////////// /// @brief parses the body //////////////////////////////////////////////////////////////////////////////// TRI_json_t* RestVocbaseBaseHandler::parseJsonBody () { char* errmsg = 0; TRI_json_t* json = TRI_Json2String(TRI_UNKNOWN_MEM_ZONE, request->body().c_str(), &errmsg); if (json == 0) { if (errmsg == 0) { generateError(HttpResponse::BAD, TRI_ERROR_HTTP_CORRUPTED_JSON, "cannot parse json object"); } else { generateError(HttpResponse::BAD, TRI_ERROR_HTTP_CORRUPTED_JSON, errmsg); TRI_FreeString(TRI_UNKNOWN_MEM_ZONE, errmsg); } return 0; } return json; } //////////////////////////////////////////////////////////////////////////////// /// @brief sets the restult-set, needs a loaded collection //////////////////////////////////////////////////////////////////////////////// TRI_doc_mptr_t const RestVocbaseBaseHandler::findDocument (string const& doc) { TRI_doc_mptr_t document; if (_documentCollection == 0) { document._did = 0; return document; } TRI_voc_did_t id = StringUtils::uint64(doc); // ............................................................................. // inside read transaction // ............................................................................. _documentCollection->beginRead(_documentCollection); document = _documentCollection->read(_documentCollection, id); // keep the oldest barrier if (_barrier != 0) { _barrier = TRI_CreateBarrierElement(&_documentCollection->_barrierList); } _documentCollection->endRead(_documentCollection); // ............................................................................. // outside read transaction // ............................................................................. return document; } //////////////////////////////////////////////////////////////////////////////// /// @brief parses a document handle //////////////////////////////////////////////////////////////////////////////// int RestVocbaseBaseHandler::parseDocumentId (string const& handle, TRI_voc_cid_t& cid, TRI_voc_did_t& did) { vector split; int res; split = StringUtils::split(handle, '/'); if (split.size() != 2) { return TRI_set_errno(TRI_ERROR_ARANGO_DOCUMENT_HANDLE_BAD); } cid = TRI_UInt64String(split[0].c_str()); res = TRI_errno(); if (res != TRI_ERROR_NO_ERROR) { return res; } did = TRI_UInt64String(split[1].c_str()); return TRI_errno(); } //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // --SECTION-- HANDLER // ----------------------------------------------------------------------------- // ----------------------------------------------------------------------------- // --SECTION-- public methods // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @addtogroup ArangoDB /// @{ //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// {@inheritDoc} //////////////////////////////////////////////////////////////////////////////// bool RestVocbaseBaseHandler::isDirect () { return false; } //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// // Local Variables: // mode: outline-minor // outline-regexp: "^\\(/// @brief\\|/// {@inheritDoc}\\|/// @addtogroup\\|// --SECTION--\\|/// @\\}\\)" // End: