/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true, evil: true */ /*global require */ //////////////////////////////////////////////////////////////////////////////// /// @brief traversal actions /// /// @file /// /// DISCLAIMER /// /// Copyright 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 Jan Steemann /// @author Copyright 2013, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// var arangodb = require("org/arangodb"); var actions = require("org/arangodb/actions"); var db = require("internal").db; var traversal = require("org/arangodb/graph/traversal"); var Traverser = traversal.Traverser; // ----------------------------------------------------------------------------- // --SECTION-- private functions // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @addtogroup ArangoAPI /// @{ //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief create a "bad parameter" error //////////////////////////////////////////////////////////////////////////////// function badParam (req, res, message) { actions.resultBad(req, res, arangodb.ERROR_HTTP_BAD_PARAMETER, message); } //////////////////////////////////////////////////////////////////////////////// /// @brief create a "not found" error //////////////////////////////////////////////////////////////////////////////// function notFound (req, res, code, message) { actions.resultNotFound(req, res, code, message); } //////////////////////////////////////////////////////////////////////////////// /// @brief execute a server-side traversal /// /// @RESTHEADER{POST /_api/traversal,executes a traversal} /// /// @RESTBODYPARAM{body,string,required} /// /// TODO: /// startVertex: id of the startVertex, e.g. "users/foo", mandatory /// edgeCollection: name of the collection that contains the edges, mandatory /// filter: body (JavaScript code) of custom filter function, optional /// function signature: (config, vertex, path) -> mixed /// can return either undefined (=include), 'exclude', 'prune' or an array of these /// if no filter is specified, all nodes will be included /// minDepth: optional minDepth filter value (will be ANDed with any existing filters) /// maxDepth: optional maxDepth filter value (will be ANDed with any existing filters) /// visitor: body (JavaScript) code of custom visitor function, optional /// function signature: (config, result, vertex, path) -> void /// visitor function can do anything, but its return value is ignored. To /// populate a result, use the "result" variable by reference /// example: "result.visited++; result.myVertices.push(vertex);" /// direction: direction for traversal, optional /// if set, must be either "outbound", "inbound", or "any" /// if not set, the "expander" attribute must be specified /// init: body (JavaScript) code of custom result initialisation function, optional /// function signature: (config, result) -> void /// initialise any values in result with what is required /// example: "result.visited = 0; result.myVertices = [ ];" /// expander: body (JavaScript) code of custom expander function, optional /// must be set if "direction" attribute is not set /// function signature: (config, vertex, path) -> array /// expander must return an array of the connections for "vertex" /// each connection is an object with the attributes "edge" and "vertex" /// example: "expander": "var connections = [ ]; config.edgeCollection.outEdges(vertex).forEach(function (e) { connections.push({ vertex: e._to, edge: e }); }); return connections;" /// strategy: traversal strategy, optional /// can be "depthfirst" or "breadthfirst" /// order: traversal order, optional /// can be "preorder" or "postorder" /// itemOrder: item iteration order, optional /// can be "forward" or "backward" /// uniqueness: specifies uniqueness for vertices and edges visited, optional /// if set, must be an object like this: "uniqueness": { "vertices": "none"|"global"|path", "edges": "none"|"global"|"path" } /// /// maxIterations: Maximum number of iterations in each traversal. This number can be /// set to prevent endless loops in traversal of cyclic graphs. When a traversal performs /// as many iterations as the `maxIterations` value, the traversal will abort with an /// error. If `maxIterations` is not set, a server-defined value may be used. /// /// the "result" object will be returned by the traversal /// /// example: /// POST {"edgeCollection": "e", "expander": "var connections = [ ]; config.edgeCollection.outEdges(vertex).forEach(function (e) { connections.push({ vertex: e._to, edge: e }); }); return connections;", "startVertex" : "v/jan", "filter": "return undefined;", "minDepth": 0 } /// /// @RESTDESCRIPTION /// TODO //////////////////////////////////////////////////////////////////////////////// function post_api_traversal(req, res) { var json = actions.getJsonBody(req, res); if (json === undefined) { return badParam(req, res); } // check start vertex // ----------------------------------------- if (json.startVertex === undefined || typeof json.startVertex !== "string") { return badParam(req, res, "missing or invalid startVertex"); } var doc; try { doc = db._document(json.startVertex); } catch (err) { return notFound(req, res, arangodb.ERROR_ARANGO_DOCUMENT_NOT_FOUND, "invalid startVertex"); } // check edge collection // ----------------------------------------- if (json.edgeCollection === undefined || typeof json.edgeCollection !== "string") { return badParam(req, res, "missing or invalid edgeCollection"); } var edgeCollection; try { edgeCollection = db._collection(json.edgeCollection); } catch (err2) { } if (edgeCollection === undefined || edgeCollection === null) { return notFound(req, res, arangodb.ERROR_ARANGO_COLLECTION_NOT_FOUND, "invalid edgeCollection"); } // set up filters // ----------------------------------------- var filters = [ ]; if (json.minDepth !== undefined) { filters.push(traversal.minDepthFilter); } if (json.maxDepth !== undefined) { filters.push(traversal.maxDepthFilter); } if (json.filter) { try { filters.push(new Function('config', 'vertex', 'path', json.filter)); } catch (err3) { return badParam(req, res, "invalid filter function"); } } // if no filter given, use the default filter (does nothing) if (filters.length === 0) { filters.push(traversal.visitAllFilter); } // set up visitor // ----------------------------------------- var visitor; if (json.visitor !== undefined) { try { visitor = new Function('config', 'result', 'vertex', 'path', json.visitor); } catch (err4) { return badParam(req, res, "invalid visitor function"); } } else { visitor = traversal.trackingVisitor; } // set up expander // ----------------------------------------- var expander; if (json.direction !== undefined) { expander = json.direction; } else if (json.expander !== undefined) { try { expander = new Function('config', 'vertex', 'path', json.expander); } catch (err5) { return badParam(req, res, "invalid expander function"); } } if (expander === undefined) { return badParam(req, res, "missing or invalid expander"); } // assemble config object // ----------------------------------------- var config = { params: json, edgeCollection: edgeCollection, datasource: traversal.collectionDatasourceFactory(edgeCollection), strategy: json.strategy, order: json.order, itemOrder: json.itemOrder, expander: expander, visitor: visitor, filter: filters, minDepth: json.minDepth, maxDepth: json.maxDepth, maxIterations: json.maxIterations, uniqueness: json.uniqueness }; // assemble result object // ----------------------------------------- var result = { visited: { vertices: [ ], paths: [ ] } }; if (json.init !== undefined) { try { var init = new Function('result', json.init); init(result); } catch (err6) { return badParam(req, res, "invalid init function"); } } // run the traversal // ----------------------------------------- var traverser; try { traverser = new Traverser(config); traverser.traverse(result, doc); actions.resultOk(req, res, actions.HTTP_OK, { result : result }); } catch (err7) { if (traverser === undefined) { // error during traversal setup return badParam(req, res, err7); } actions.resultException(req, res, err7, undefined, false); } } //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // --SECTION-- initialiser // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @brief gateway //////////////////////////////////////////////////////////////////////////////// actions.defineHttp({ url : "_api/traversal", context : "api", callback : function (req, res) { try { switch (req.requestType) { case actions.POST: post_api_traversal(req, res); break; default: actions.resultUnsupported(req, res); } } catch (err) { actions.resultException(req, res, err); } } }); //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// // Local Variables: // mode: outline-minor // outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)" // End: