//////////////////////////////////////////////////////////////////////////////// /// @brief abstract base request handler /// /// @file /// /// DISCLAIMER /// /// Copyright 2004-2013 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-2013, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// #include "RestVocbaseBaseHandler.h" #include "Basics/JsonHelper.h" #include "Basics/StringUtils.h" #include "BasicsC/conversions.h" #include "BasicsC/string-buffer.h" #include "BasicsC/tri-strings.h" #include "Rest/HttpRequest.h" #include "ShapedJson/shaped-json.h" #include "Utils/DocumentHelper.h" #include "VocBase/primary-collection.h" #include "VocBase/document-collection.h" #include "RestServer/VocbaseManager.h" #include "RestServer/VocbaseContext.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 batch path //////////////////////////////////////////////////////////////////////////////// string RestVocbaseBaseHandler::BATCH_PATH = "/_api/batch"; //////////////////////////////////////////////////////////////////////////////// /// @brief collection path //////////////////////////////////////////////////////////////////////////////// string RestVocbaseBaseHandler::COLLECTION_PATH = "/_api/collection"; //////////////////////////////////////////////////////////////////////////////// /// @brief document path //////////////////////////////////////////////////////////////////////////////// string RestVocbaseBaseHandler::DOCUMENT_PATH = "/_api/document"; //////////////////////////////////////////////////////////////////////////////// /// @brief documents import path //////////////////////////////////////////////////////////////////////////////// string RestVocbaseBaseHandler::DOCUMENT_IMPORT_PATH = "/_api/import"; //////////////////////////////////////////////////////////////////////////////// /// @brief document path //////////////////////////////////////////////////////////////////////////////// string RestVocbaseBaseHandler::EDGE_PATH = "/_api/edge"; //////////////////////////////////////////////////////////////////////////////// /// @brief replication path //////////////////////////////////////////////////////////////////////////////// string RestVocbaseBaseHandler::REPLICATION_PATH = "/_api/replication"; //////////////////////////////////////////////////////////////////////////////// /// @brief upload path //////////////////////////////////////////////////////////////////////////////// string RestVocbaseBaseHandler::UPLOAD_PATH = "/_api/upload"; //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // --SECTION-- constructors and destructors // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @addtogroup ArangoDB /// @{ //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief constructor //////////////////////////////////////////////////////////////////////////////// RestVocbaseBaseHandler::RestVocbaseBaseHandler (HttpRequest* request, TRI_vocbase_t* vocbase) : RestBaseHandler(request), _vocbase(vocbase), _resolver(vocbase), _timing(), _timingResult(RES_FAIL) { RequestContext* rc = request->getRequestContext(); _context = static_cast(rc); // overwrite vocbase if (_context && _context->getVocbase()) { _vocbase = _context->getVocbase(); _resolver = _context->getVocbase(); } } //////////////////////////////////////////////////////////////////////////////// /// @brief destructor //////////////////////////////////////////////////////////////////////////////// RestVocbaseBaseHandler::~RestVocbaseBaseHandler () { LOGGER_REQUEST_IN_END_I(_timing, _timingResult); } //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // --SECTION-- protected methods // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @addtogroup ArangoDB /// @{ //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief check if a collection needs to be created on the fly /// /// this method will check the "createCollection" attribute of the request. if /// it is set to true, it will verify that the named collection actually exists. /// if the collection does not yet exist, it will create it on the fly. /// if the "createCollection" attribute is not set or set to false, nothing will /// happen, and the collection name will not be checked //////////////////////////////////////////////////////////////////////////////// bool RestVocbaseBaseHandler::checkCreateCollection (const string& name, const TRI_col_type_e type) { bool found; char const* valueStr = _request->value("createCollection", found); if (! found) { // "createCollection" parameter not specified return true; } if (! StringUtils::boolean(valueStr)) { // "createCollection" parameter specified, but with non-true value return true; } TRI_vocbase_col_t* collection = TRI_FindCollectionByNameOrCreateVocBase(_vocbase, name.c_str(), type, TRI_GetServerId()); if (collection == 0) { generateTransactionError(name, TRI_errno()); return false; } return true; } //////////////////////////////////////////////////////////////////////////////// /// @brief generates a HTTP 201 or 202 response //////////////////////////////////////////////////////////////////////////////// void RestVocbaseBaseHandler::generate20x (const HttpResponse::HttpResponseCode responseCode, const string& collectionName, const TRI_voc_key_t key, const TRI_voc_rid_t rid) { const string handle = DocumentHelper::assembleDocumentId(collectionName, key); const string rev = StringUtils::itoa(rid); _response = createResponse(responseCode); _response->setContentType("application/json; charset=utf-8"); if (responseCode != HttpResponse::OK) { // 200 OK is sent is case of delete or update. // in these cases we do not return etag nor location _response->setHeader("etag", 4, "\"" + rev + "\""); // handle does not need to be RFC 2047-encoded _response->setHeader("location", 8, DOCUMENT_PATH + "/" + handle); } // _id and _key are safe and do not need to be JSON-encoded _response->body() .appendText("{\"error\":false,\"_id\":\"") .appendText(handle) .appendText("\",\"" TRI_VOC_ATTRIBUTE_REV "\":\"") .appendText(rev) .appendText("\",\"" TRI_VOC_ATTRIBUTE_KEY "\":\"") .appendText(key) .appendText("\"}"); } //////////////////////////////////////////////////////////////////////////////// /// @brief generates document not found error message //////////////////////////////////////////////////////////////////////////////// void RestVocbaseBaseHandler::generateDocumentNotFound (const TRI_voc_cid_t cid, TRI_voc_key_t key) { generateError(HttpResponse::NOT_FOUND, TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND, "document " + DOCUMENT_PATH + "/" + DocumentHelper::assembleDocumentId(_resolver.getCollectionName(cid), key) + " not found"); } //////////////////////////////////////////////////////////////////////////////// /// @brief generates not implemented //////////////////////////////////////////////////////////////////////////////// void RestVocbaseBaseHandler::generateNotImplemented (const string& 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 (const TRI_voc_cid_t cid, TRI_voc_key_t key, TRI_voc_rid_t rid) { _response = createResponse(HttpResponse::PRECONDITION_FAILED); _response->setContentType("application/json; charset=utf-8"); // _id and _key are safe and do not need to be JSON-encoded _response->body() .appendText("{\"error\":true,\"code\":") .appendInteger((int32_t) HttpResponse::PRECONDITION_FAILED) .appendText(",\"errorNum\":") .appendInteger((int32_t) TRI_ERROR_ARANGO_CONFLICT) .appendText(",\"errorMessage\":\"precondition failed\"") .appendText(",\"_id\":\"") .appendText(DocumentHelper::assembleDocumentId(_resolver.getCollectionName(cid), key)) .appendText("\",\"" TRI_VOC_ATTRIBUTE_REV "\":\"") .appendText(StringUtils::itoa(rid)) .appendText("\",\"" TRI_VOC_ATTRIBUTE_KEY "\":\"") .appendText(key) .appendText("\"}"); } //////////////////////////////////////////////////////////////////////////////// /// @brief generates not modified //////////////////////////////////////////////////////////////////////////////// void RestVocbaseBaseHandler::generateNotModified (const TRI_voc_rid_t rid) { const string rev = StringUtils::itoa(rid); _response = createResponse(HttpResponse::NOT_MODIFIED); _response->setHeader("etag", 4, "\"" + rev + "\""); } //////////////////////////////////////////////////////////////////////////////// /// @brief generates next entry from a result set //////////////////////////////////////////////////////////////////////////////// void RestVocbaseBaseHandler::generateDocument (const TRI_voc_cid_t cid, TRI_doc_mptr_t const* document, TRI_shaper_t* shaper, const bool generateBody) { if (document == 0) { generateError(HttpResponse::SERVER_ERROR, TRI_ERROR_INTERNAL, "document pointer is null, should not happen"); return; } const string id = DocumentHelper::assembleDocumentId(_resolver.getCollectionName(cid), document->_key); TRI_json_t augmented; TRI_InitArray2Json(TRI_UNKNOWN_MEM_ZONE, &augmented, 5); TRI_json_t* _id = TRI_CreateString2CopyJson(TRI_UNKNOWN_MEM_ZONE, id.c_str(), id.size()); if (_id) { TRI_Insert2ArrayJson(TRI_UNKNOWN_MEM_ZONE, &augmented, "_id", _id); } // convert rid from uint64_t to string const string rid = StringUtils::itoa(document->_rid); TRI_json_t* _rev = TRI_CreateString2CopyJson(TRI_UNKNOWN_MEM_ZONE, rid.c_str(), rid.size()); if (_rev) { TRI_Insert2ArrayJson(TRI_UNKNOWN_MEM_ZONE, &augmented, TRI_VOC_ATTRIBUTE_REV, _rev); } TRI_json_t* _key = TRI_CreateString2CopyJson(TRI_UNKNOWN_MEM_ZONE, document->_key, strlen(document->_key)); if (_key) { TRI_Insert2ArrayJson(TRI_UNKNOWN_MEM_ZONE, &augmented, TRI_VOC_ATTRIBUTE_KEY, _key); } TRI_df_marker_type_t type = ((TRI_df_marker_t*) document->_data)->_type; if (type == TRI_DOC_MARKER_KEY_EDGE) { TRI_doc_edge_key_marker_t* marker = (TRI_doc_edge_key_marker_t*) document->_data; const string from = DocumentHelper::assembleDocumentId(_resolver.getCollectionName(marker->_fromCid), string((char*) marker + marker->_offsetFromKey)); const string to = DocumentHelper::assembleDocumentId(_resolver.getCollectionName(marker->_toCid), string((char*) marker + marker->_offsetToKey)); TRI_Insert3ArrayJson(TRI_UNKNOWN_MEM_ZONE, &augmented, TRI_VOC_ATTRIBUTE_FROM, TRI_CreateString2CopyJson(TRI_UNKNOWN_MEM_ZONE, from.c_str(), from.size())); TRI_Insert3ArrayJson(TRI_UNKNOWN_MEM_ZONE, &augmented, TRI_VOC_ATTRIBUTE_TO, TRI_CreateString2CopyJson(TRI_UNKNOWN_MEM_ZONE, to.c_str(), to.size())); } // add document identifier to buffer TRI_string_buffer_t buffer; // convert object to string TRI_InitStringBuffer(&buffer, TRI_UNKNOWN_MEM_ZONE); TRI_shaped_json_t shapedJson; TRI_EXTRACT_SHAPED_JSON_MARKER(shapedJson, document->_data); TRI_StringifyAugmentedShapedJson(shaper, &buffer, &shapedJson, &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); } if (_key) { TRI_Free(TRI_UNKNOWN_MEM_ZONE, _key); } // and generate a response _response = createResponse(HttpResponse::OK); _response->setContentType("application/json; charset=utf-8"); _response->setHeader("etag", 4, "\"" + rid + "\""); if (generateBody) { _response->body().appendText(TRI_BeginStringBuffer(&buffer), TRI_LengthStringBuffer(&buffer)); } else { _response->headResponse(TRI_LengthStringBuffer(&buffer)); } TRI_DestroyStringBuffer(&buffer); } //////////////////////////////////////////////////////////////////////////////// /// @brief generate an error message for a transaction error //////////////////////////////////////////////////////////////////////////////// void RestVocbaseBaseHandler::generateTransactionError (const string& collectionName, const int res, TRI_voc_key_t key, TRI_voc_rid_t rid) { switch (res) { case TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND: if (collectionName.empty()) { // no collection name specified generateError(HttpResponse::BAD, res, "no collection name specified"); } else { // collection name specified but collection not found generateError(HttpResponse::NOT_FOUND, res, "collection " + COLLECTION_PATH + "/" + collectionName + " not found"); } return; case TRI_ERROR_ARANGO_READ_ONLY: generateError(HttpResponse::FORBIDDEN, res, "collection is read-only"); return; case TRI_ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED: generateError(HttpResponse::CONFLICT, res, "cannot create document, unique constraint violated"); return; case TRI_ERROR_ARANGO_GEO_INDEX_VIOLATED: generateError(HttpResponse::BAD, res, "geo constraint violated"); return; case TRI_ERROR_ARANGO_DOCUMENT_KEY_BAD: generateError(HttpResponse::BAD, res, "invalid document key"); return; case TRI_ERROR_ARANGO_OUT_OF_KEYS: generateError(HttpResponse::SERVER_ERROR, res, "out of keys"); return; case TRI_ERROR_ARANGO_DOCUMENT_KEY_UNEXPECTED: generateError(HttpResponse::BAD, res, "collection does not allow using user-defined keys"); return; case TRI_ERROR_ARANGO_DOCUMENT_NOT_FOUND: generateDocumentNotFound(_resolver.getCollectionId(collectionName), key); return; case TRI_ERROR_ARANGO_DOCUMENT_TYPE_INVALID: generateError(HttpResponse::BAD, res); return; case TRI_ERROR_ARANGO_CONFLICT: generatePreconditionFailed(_resolver.getCollectionId(collectionName), key ? key : (TRI_voc_key_t) "unknown", rid); return; default: generateError(HttpResponse::SERVER_ERROR, TRI_ERROR_INTERNAL, "failed with error: " + string(TRI_errno_string(res))); } } //////////////////////////////////////////////////////////////////////////////// /// @brief extracts the revision //////////////////////////////////////////////////////////////////////////////// TRI_voc_rid_t RestVocbaseBaseHandler::extractRevision (char const* header, char const* parameter) { bool found; char const* etag = _request->header(header, found); if (found) { char const* s = etag; char const* e = etag + strlen(etag); while (s < e && (s[0] == ' ' || s[0] == '\t')) { ++s; } while (s < e && (e[-1] == ' ' || e[-1] == '\t')) { --e; } if (s + 1 < e && s[0] == '"' && e[-1] == '"') { return TRI_UInt64String2(s + 1, e - s - 2); } else { return 0; } } if (parameter == 0) { return 0; } else { etag = _request->value(parameter, found); if (found) { return TRI_UInt64String(etag); } else { return 0; } } } //////////////////////////////////////////////////////////////////////////////// /// @brief extracts the update policy //////////////////////////////////////////////////////////////////////////////// TRI_doc_update_policy_e RestVocbaseBaseHandler::extractUpdatePolicy () const { bool found; char const* policy = _request->value("policy", found); if (found) { if (TRI_CaseEqualString(policy, "error")) { return TRI_DOC_UPDATE_ERROR; } else if (TRI_CaseEqualString(policy, "last")) { return TRI_DOC_UPDATE_LAST_WRITE; } else { return TRI_DOC_UPDATE_ILLEGAL; } } else { return TRI_DOC_UPDATE_ERROR; } } //////////////////////////////////////////////////////////////////////////////// /// @brief extracts the waitForSync value //////////////////////////////////////////////////////////////////////////////// bool RestVocbaseBaseHandler::extractWaitForSync () const { bool found; char const* forceStr = _request->value("waitForSync", found); if (found) { return StringUtils::boolean(forceStr); } return false; } //////////////////////////////////////////////////////////////////////////////// /// @brief parses the body //////////////////////////////////////////////////////////////////////////////// TRI_json_t* RestVocbaseBaseHandler::parseJsonBody () { char* errmsg = 0; TRI_json_t* json = _request->toJson(&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_CORE_MEM_ZONE, errmsg); } return 0; } if (TRI_HasDuplicateKeyJson(json)) { generateError(HttpResponse::BAD, TRI_ERROR_HTTP_CORRUPTED_JSON, "cannot parse json object"); TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); return 0; } return json; } //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // --SECTION-- HANDLER // ----------------------------------------------------------------------------- // ----------------------------------------------------------------------------- // --SECTION-- public methods // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @addtogroup ArangoDB /// @{ //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// {@inheritDoc} //////////////////////////////////////////////////////////////////////////////// bool RestVocbaseBaseHandler::isDirect () { return false; } //////////////////////////////////////////////////////////////////////////////// /// @brief extract a string attribute from a JSON array /// /// if the attribute is not there or not a string, this returns 0 //////////////////////////////////////////////////////////////////////////////// char const* RestVocbaseBaseHandler::extractJsonStringValue (const TRI_json_t* const json, char const* name) { if (json == 0 || json->_type != TRI_JSON_ARRAY) { return 0; } TRI_json_t* value = TRI_LookupArrayJson(json, name); if (! JsonHelper::isString(value)) { return 0; } return value->_value._string.data; } //////////////////////////////////////////////////////////////////////////////// /// @brief parses a document handle //////////////////////////////////////////////////////////////////////////////// int RestVocbaseBaseHandler::parseDocumentId (string const& handle, TRI_voc_cid_t& cid, TRI_voc_key_t& key) { vector split; split = StringUtils::split(handle, '/'); if (split.size() != 2) { return TRI_set_errno(TRI_ERROR_ARANGO_DOCUMENT_HANDLE_BAD); } cid = _resolver.getCollectionId(split[0]); if (cid == 0) { return TRI_ERROR_ARANGO_COLLECTION_NOT_FOUND; } key = TRI_DuplicateStringZ(TRI_CORE_MEM_ZONE, split[1].c_str()); return TRI_ERROR_NO_ERROR; } //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// // Local Variables: // mode: outline-minor // outline-regexp: "/// @brief\\|/// {@inheritDoc}\\|/// @addtogroup\\|/// @page\\|// --SECTION--\\|/// @\\}" // End: