All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.rest.graphdb.query.CypherTransaction Maven / Gradle / Ivy

Go to download

pring Data Neo4j Wrapper for the Neo4j REST API, provides a Graph Database proxy for the remote invocation.

There is a newer version: 3.4.6.RELEASE
Show newest version
package org.neo4j.rest.graphdb.query;

import com.sun.jersey.api.client.ClientResponse;
import org.neo4j.helpers.collection.IterableWrapper;
import org.neo4j.helpers.collection.IteratorWrapper;
import org.neo4j.rest.graphdb.*;

import javax.ws.rs.core.Response;
import java.util.*;

import static java.util.Arrays.asList;
import static org.neo4j.helpers.collection.MapUtil.map;
import static org.neo4j.helpers.collection.MapUtil.stringMap;

/**
 * @author mh
 * @since 24.09.14
 */
public class CypherTransaction {

    @SuppressWarnings("unchecked")
    public enum ResultType {
        /** all nodes and rels collated in one bag **/
        graph() {
            @Override
            public List get(Map data) {
                Map graph = (Map) data.get(name());
                List result = new ArrayList((List)graph.get("nodes"));
                result.addAll((List) graph.get("relationships"));
                return result;
            }
            public List getNodes(Map data) {
                Map graph = (Map) data.get(name());
                return (List)graph.get("nodes");
            }
            public List getRelationships(Map data) {
                Map graph = (Map) data.get(name());
                return (List)graph.get("relationships");
            }
        }, row, rest;

        public List get(Map data) {
            return (List) data.get(name());
        }
    }

    public CypherTransaction(String baseUri, ResultType type) {
        this.type = type;
        this.request = new ExecutingRestRequest(baseUri);
    }
    public CypherTransaction(RestAPICypherImpl restAPI, ResultType type) {
        this.type = type;
        this.request = restAPI.getRestRequest();
    }

    public static class Result implements Iterable> {
        private final List columns;
        private final Iterable> rows;
        private final Statement statement;

        Result(List columns, Iterable> rows, Statement statement) {
            this.columns = columns;
            this.rows = rows;
            this.statement = statement;
        }

        private static List toResults(List resultsData, List statements, ResultType type) {
            List results=new ArrayList<>();
            for (int i = 0; i < resultsData.size(); i++) {
                results.add(toResult(resultsData.get(i), statements.get(i), type));
            }
            return results;
        }

        @SuppressWarnings("unchecked")
        private static Result toResult(Map resultData, Statement statement, final ResultType type) {
            List columns = (List) resultData.get("columns");
            List rowsData = (List) resultData.get("data");
            final boolean replace = statement.doReplace();
            Iterable> rows = new IterableWrapper,Map>(rowsData) {
                protected List underlyingObjectToObject(Map map) {
                    List row = type.get(map);
                    List graph = ResultType.graph.get(map);
                    if (replace) replaceGraphElements(row, (List) graph);
                    return row;
                }
            };
            return new Result(columns, rows, statement);
        }

        // todo hack !!
        private static void replaceGraphElements(List row, List graph) {
            for (Map pc : graph) {
                Object props = pc.get("properties");
                int pos = -1;
                for (int i = 0; i < row.size(); i++) {
                    Object o = row.get(i);
                    if (props.equals(o)) {
//                        row.set(i,pc);
                        if (pos == -1) pos = i; else pos = -2;
                    }
                }
                if (pos >= 0) row.set(pos,pc);
            }
        }

        public List getColumns() {
            return columns;
        }

        public Iterable> getRows() {
            return rows;
        }

        public Statement getStatement() {
            return statement;
        }

        @Override
        public Iterator> iterator() {
            return new IteratorWrapper,List>(rows.iterator()) {
                protected Map underlyingObjectToObject(List objects) {
                    Map row = new LinkedHashMap<>(columns.size());
                    for (int i = 0; i < columns.size(); i++) {
                        row.put(columns.get(i), objects.get(i));
                    }
                    return row;
                }
            };
        }

        public boolean hasData() {
            return rows.iterator().hasNext();
        }
    }

    public static class Statement {
        private final String statement;
        private final ResultType type;
        private final Map parameters;
        private final boolean replace;

        public Statement(String query, Map parameters, ResultType type, boolean replace) {
            this.statement = query;
            this.type = type;
            this.replace = replace;
            this.parameters = parameters == null ? Collections.emptyMap() : parameters;
        }

        public String getStatement() {
            return statement;
        }

        public Map getParameters() {
            return new LinkedHashMap<>(parameters);
        }

        public List getResultDataContents() {
            return Arrays.asList(type.name(), ResultType.graph.name());
        }

        public ResultType getType() {
            return type;
        }

        private boolean doReplace() { return replace; }
    }

    private final ResultType type;
    private String transactionUrl = null;
    private String commitUrl = null;
    private final RestRequest request;
    private final List statements = new ArrayList<>(10);

    public void addAll(Statement...statements) {
        this.statements.addAll(asList(statements));
    }

    public void addAll(Collection statements) {
        this.statements.addAll(statements);
    }

    public void add(String statement, Map params) {
        add(statement,params,false);
    }
    public void add(String statement, Map params, boolean replace) {
        statements.add(new Statement(statement,params,type, replace));
    }

    public Result send(String statement, Map params) {
        return send(statement,params,false);
    }

    public Result send(String statement, Map params, boolean replace) {
        add(statement,params, replace);
        List results = send(transactionUrl());
        if (results.size() > 0) return results.get(results.size() - 1);
        throw new CypherTransactionExecutionException("Error Sending",asList(new Statement(statement,params,type, replace)),errors("No.Results","No Results after single send"));
    }

    public Result commit(String statement, Map params) {
        return commit(statement,params,false);
    }

    public Result commit(String statement, Map params, boolean replace) {
        add(statement,params, replace);
        List results = commit();
        if (results.size() > 0) return results.get(results.size() - 1);
        else throw new CypherTransactionExecutionException("Error Sending",asList(new Statement(statement,params,type, replace)),errors("No.Results","No Results after single commit"));
    }

    public List send() {
        return send(transactionUrl());
    }

    public List commit() {
        try {
            if (statements.isEmpty()) add("return 1",null, false); // TODO hacking workaround b/c of periodic commit check in server accesses the first of an empty statement list with an NPE
            return send(commitUrl());
        } finally {
            commitUrl = null;
        }
    }

    private List send(String url) {
        try {
            RequestResult result = request.post(url, map("statements", statements));
            if (result.statusIs(Response.Status.OK) || result.statusIs(Response.Status.CREATED)) {
                ArrayList statementsCopy = new ArrayList<>(statements);
                return Result.toResults(handleResult(result,statementsCopy), statementsCopy, type);
            } else {
                List> errors = errors("Http." + result.getStatus(), result.getText());
                throw new CypherTransactionExecutionException("Error executing statements: " + result.getStatus() +
                        " " + result.getText(),statements, errors);
            }
        } finally {
            statements.clear();
        }
    }

    private List> errors(String code, String message) {
        return asList(stringMap("code", code, "message", message));
    }

    public void rollback() {
        if (transactionUrl != null) {
            request.delete(transactionUrl);
        }
        transactionUrl = null;
        commitUrl = null;
    }

    private String commitUrl() {
        return (commitUrl == null) ? "transaction/commit" : commitUrl;
    }

    private String transactionUrl() {
        return (transactionUrl==null) ? "transaction" : transactionUrl;
    }

    @SuppressWarnings("unchecked")
    private List handleResult(RequestResult result, ArrayList statements) {
        Map resultData = result.toMap();
        List> errors = (List>) resultData.get("errors");
        if (result.statusIs(ClientResponse.Status.CREATED)) transactionUrl = result.getLocation();
        if (errors != null && !errors.isEmpty()) throw new CypherTransactionExecutionException("Error executing cypher statements ",statements, errors);
        commitUrl = (String) resultData.get("commit");
        return (List) resultData.get("results");
    }

    @Override
    public String toString() {
        return "Transaction: "+transactionUrl;
    }
}