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

org.apache.metamodel.neo4j.Neo4jDataContext Maven / Gradle / Ivy

There is a newer version: 5.3.3
Show newest version
/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */
package org.apache.metamodel.neo4j;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.apache.http.HttpHost;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.metamodel.DataContext;
import org.apache.metamodel.MetaModelException;
import org.apache.metamodel.MetaModelHelper;
import org.apache.metamodel.QueryPostprocessDataContext;
import org.apache.metamodel.data.DataSet;
import org.apache.metamodel.data.DocumentSource;
import org.apache.metamodel.query.FilterItem;
import org.apache.metamodel.query.SelectItem;
import org.apache.metamodel.schema.Column;
import org.apache.metamodel.schema.MutableSchema;
import org.apache.metamodel.schema.MutableTable;
import org.apache.metamodel.schema.Schema;
import org.apache.metamodel.schema.Table;
import org.apache.metamodel.schema.builder.DocumentSourceProvider;
import org.apache.metamodel.util.SimpleTableDef;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * DataContext implementation for Neo4j
 */
public class Neo4jDataContext extends QueryPostprocessDataContext implements DataContext, DocumentSourceProvider {

    public static final Logger logger = LoggerFactory.getLogger(Neo4jDataContext.class);

    public static final String SCHEMA_NAME = "neo4j";

    public static final int DEFAULT_PORT = 7474;

    public static final String RELATIONSHIP_PREFIX = "rel_";

    public static final String RELATIONSHIP_COLUMN_SEPARATOR = "#";

    private final SimpleTableDef[] _tableDefs;

    private final Neo4jRequestWrapper _requestWrapper;

    private final HttpHost _httpHost;

    private String _serviceRoot = "/db/data";

    public Neo4jDataContext(String hostname, int port, String username, String password, SimpleTableDef... tableDefs) {
        _httpHost = new HttpHost(hostname, port);
        final CloseableHttpClient httpClient = HttpClientBuilder.create().build();
        _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, username, password, _serviceRoot);
        _tableDefs = tableDefs;
    }

    public Neo4jDataContext(String hostname, int port, String username, String password, String serviceRoot,
            SimpleTableDef... tableDefs) {
        _httpHost = new HttpHost(hostname, port);
        final CloseableHttpClient httpClient = HttpClientBuilder.create().build();
        _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, username, password, _serviceRoot);
        _tableDefs = tableDefs;
        _serviceRoot = serviceRoot;
    }

    public Neo4jDataContext(String hostname, int port, String username, String password) {
        _httpHost = new HttpHost(hostname, port);
        final CloseableHttpClient httpClient = HttpClientBuilder.create().build();
        _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, username, password, _serviceRoot);
        _tableDefs = detectTableDefs();
    }

    public Neo4jDataContext(String hostname, int port, String username, String password, String serviceRoot) {
        _httpHost = new HttpHost(hostname, port);
        final CloseableHttpClient httpClient = HttpClientBuilder.create().build();
        _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, username, password, _serviceRoot);
        _tableDefs = detectTableDefs();
        _serviceRoot = serviceRoot;
    }

    public Neo4jDataContext(String hostname, int port, CloseableHttpClient httpClient) {
        _httpHost = new HttpHost(hostname, port);
        _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, _serviceRoot);
        _tableDefs = detectTableDefs();
    }

    public Neo4jDataContext(String hostname, int port, CloseableHttpClient httpClient, String serviceRoot) {
        _httpHost = new HttpHost(hostname, port);
        _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, _serviceRoot);
        _tableDefs = detectTableDefs();
        _serviceRoot = serviceRoot;
    }

    public Neo4jDataContext(String hostname, int port, CloseableHttpClient httpClient, SimpleTableDef... tableDefs) {
        _httpHost = new HttpHost(hostname, port);
        _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, _serviceRoot);
        _tableDefs = tableDefs;
    }

    public Neo4jDataContext(String hostname, int port, CloseableHttpClient httpClient, String serviceRoot,
            SimpleTableDef... tableDefs) {
        _httpHost = new HttpHost(hostname, port);
        _requestWrapper = new Neo4jRequestWrapper(httpClient, _httpHost, _serviceRoot);
        _tableDefs = tableDefs;
        _serviceRoot = serviceRoot;
    }

    @Override
    protected String getDefaultSchemaName() throws MetaModelException {
        return SCHEMA_NAME;
    }

    @Override
    protected Schema getMainSchema() throws MetaModelException {
        MutableSchema schema = new MutableSchema(getMainSchemaName());
        for (SimpleTableDef tableDef : _tableDefs) {
            MutableTable table = tableDef.toTable().setSchema(schema);
            schema.addTable(table);
        }
        return schema;
    }

    @Override
    protected String getMainSchemaName() throws MetaModelException {
        return SCHEMA_NAME;
    }

    public SimpleTableDef[] detectTableDefs() {
        List tableDefs = new ArrayList();

        String labelsJsonString = _requestWrapper.executeRestRequest(new HttpGet(_serviceRoot + "/labels"));

        JSONArray labelsJsonArray;
        try {
            labelsJsonArray = new JSONArray(labelsJsonString);
            for (int i = 0; i < labelsJsonArray.length(); i++) {
                String label = labelsJsonArray.getString(i);

                List nodesPerLabel = getAllNodesPerLabel(label);

                List propertiesPerLabel = new ArrayList();
                for (JSONObject node : nodesPerLabel) {
                    List propertiesPerNode = getAllPropertiesPerNode(node);
                    for (String property : propertiesPerNode) {
                        if (!propertiesPerLabel.contains(property)) {
                            propertiesPerLabel.add(property);
                        }
                    }
                }

                Set relationshipPropertiesPerLabel = new LinkedHashSet();
                for (JSONObject node : nodesPerLabel) {
                    Integer nodeId = (Integer) node.getJSONObject("metadata").get("id");
                    List relationshipsPerNode = getOutgoingRelationshipsPerNode(nodeId);
                    for (JSONObject relationship : relationshipsPerNode) {
                        // Add the relationship as a column in the table
                        String relationshipName = relationship.getString("type");
                        String relationshipNameProperty = RELATIONSHIP_PREFIX + relationshipName;
                        if (!relationshipPropertiesPerLabel.contains(relationshipNameProperty)) {
                            relationshipPropertiesPerLabel.add(relationshipNameProperty);
                        }

                        // Add all the relationship properties as table columns
                        List propertiesPerRelationship = getAllPropertiesPerRelationship(relationship);
                        relationshipPropertiesPerLabel.addAll(propertiesPerRelationship);
                    }
                }
                propertiesPerLabel.addAll(relationshipPropertiesPerLabel);

                // Do not add a table if label has no nodes (empty tables are
                // considered non-existent)
                if (!nodesPerLabel.isEmpty()) {
                    SimpleTableDef tableDef = new SimpleTableDef(label,
                            propertiesPerLabel.toArray(new String[propertiesPerLabel.size()]));
                    tableDefs.add(tableDef);
                }
            }
            return tableDefs.toArray(new SimpleTableDef[tableDefs.size()]);
        } catch (JSONException e) {
            logger.error("Error occured in parsing JSON while detecting the schema: ", e);
            throw new IllegalStateException(e);
        }
    }

    private List getAllPropertiesPerRelationship(JSONObject relationship) {
        List propertyNames = new ArrayList();
        try {
            String relationshipName = RELATIONSHIP_PREFIX + relationship.getJSONObject("metadata").getString("type");
            JSONObject relationshipPropertiesJSONObject = relationship.getJSONObject("data");
            if (relationshipPropertiesJSONObject.length() > 0) {
                JSONArray relationshipPropertiesNamesJSONArray = relationshipPropertiesJSONObject.names();
                for (int i = 0; i < relationshipPropertiesNamesJSONArray.length(); i++) {
                    String propertyName = relationshipName + RELATIONSHIP_COLUMN_SEPARATOR
                            + relationshipPropertiesNamesJSONArray.getString(i);
                    if (!propertyNames.contains(propertyName)) {
                        propertyNames.add(propertyName);
                    }
                }
            }
            return propertyNames;
        } catch (JSONException e) {
            logger.error("Error occured in parsing JSON while getting relationship properties: ", e);
            throw new IllegalStateException(e);
        }
    }

    private List getOutgoingRelationshipsPerNode(Integer nodeId) {
        List outgoingRelationshipsPerNode = new ArrayList();

        String outgoingRelationshipsPerNodeJsonString = _requestWrapper.executeRestRequest(new HttpGet(_serviceRoot
                + "/node/" + nodeId + "/relationships/out"));

        JSONArray outgoingRelationshipsPerNodeJsonArray;
        try {
            outgoingRelationshipsPerNodeJsonArray = new JSONArray(outgoingRelationshipsPerNodeJsonString);
            for (int i = 0; i < outgoingRelationshipsPerNodeJsonArray.length(); i++) {
                JSONObject relationship = outgoingRelationshipsPerNodeJsonArray.getJSONObject(i);
                if (!outgoingRelationshipsPerNode.contains(relationship)) {
                    outgoingRelationshipsPerNode.add(relationship);
                }
            }
            return outgoingRelationshipsPerNode;
        } catch (JSONException e) {
            logger.error("Error occured in parsing JSON while detecting outgoing relationships for node: " + nodeId, e);
            throw new IllegalStateException(e);
        }
    }

    private List getAllNodesPerLabel(String label) {
        List allNodesPerLabel = new ArrayList();

        String allNodesForLabelJsonString = _requestWrapper.executeRestRequest(new HttpGet(_serviceRoot + "/label/"
                + label + "/nodes"));

        JSONArray allNodesForLabelJsonArray;
        try {
            allNodesForLabelJsonArray = new JSONArray(allNodesForLabelJsonString);
            for (int i = 0; i < allNodesForLabelJsonArray.length(); i++) {
                JSONObject node = allNodesForLabelJsonArray.getJSONObject(i);
                allNodesPerLabel.add(node);
            }
            return allNodesPerLabel;
        } catch (JSONException e) {
            logger.error("Error occured in parsing JSON while detecting the nodes for a label: " + label, e);
            throw new IllegalStateException(e);
        }
    }

    private List getAllPropertiesPerNode(JSONObject node) {
        List properties = new ArrayList();
        properties.add("_id");

        String propertiesEndpoint;
        try {
            propertiesEndpoint = node.getString("properties");

            String allPropertiesPerNodeJsonString = _requestWrapper.executeRestRequest(new HttpGet(propertiesEndpoint));

            JSONObject allPropertiesPerNodeJsonObject = new JSONObject(allPropertiesPerNodeJsonString);
            for (int j = 0; j < allPropertiesPerNodeJsonObject.length(); j++) {
                JSONArray propertiesJsonArray = allPropertiesPerNodeJsonObject.names();
                for (int k = 0; k < propertiesJsonArray.length(); k++) {
                    String property = propertiesJsonArray.getString(k);
                    properties.add(property);
                }
            }
            return properties;
        } catch (JSONException e) {
            logger.error("Error occured in parsing JSON while detecting the properties of a node: " + node, e);
            throw new IllegalStateException(e);
        }
    }

    @Override
    protected DataSet materializeMainSchemaTable(Table table, Column[] columns, int firstRow, int maxRows) {
        if ((columns != null) && (columns.length > 0)) {
            Neo4jDataSet dataSet = null;
            try {
                String selectQuery = Neo4jCypherQueryBuilder.buildSelectQuery(table, columns, firstRow, maxRows);
                String responseJSONString = _requestWrapper.executeCypherQuery(selectQuery);
                JSONObject resultJSONObject = new JSONObject(responseJSONString);
                final SelectItem[] selectItems = MetaModelHelper.createSelectItems(columns);
                dataSet = new Neo4jDataSet(selectItems, resultJSONObject);
            } catch (JSONException e) {
                logger.error("Error occured in parsing JSON while materializing the schema: ", e);
                throw new IllegalStateException(e);
            }

            return dataSet;
        } else {
            logger.error("Encountered null or empty columns array for materializing main schema table.");
            throw new IllegalArgumentException("Columns cannot be null or empty array");
        }
    }

    @Override
    protected DataSet materializeMainSchemaTable(Table table, Column[] columns, int maxRows) {
        return materializeMainSchemaTable(table, columns, 1, maxRows);
    }

    @Override
    protected Number executeCountQuery(Table table, List whereItems, boolean functionApproximationAllowed) {
        String countQuery = Neo4jCypherQueryBuilder.buildCountQuery(table.getName(), whereItems);
        String jsonResponse = _requestWrapper.executeCypherQuery(countQuery);

        JSONObject jsonResponseObject;
        try {
            jsonResponseObject = new JSONObject(jsonResponse);
            JSONArray resultsJSONArray = jsonResponseObject.getJSONArray("results");
            JSONObject resultJSONObject = (JSONObject) resultsJSONArray.get(0);
            JSONArray dataJSONArray = resultJSONObject.getJSONArray("data");
            JSONObject rowJSONObject = (JSONObject) dataJSONArray.get(0);
            JSONArray valueJSONArray = rowJSONObject.getJSONArray("row");
            Number value = (Number) valueJSONArray.get(0);
            return value;
        } catch (JSONException e) {
            logger.error("Error occured in parsing JSON response: ", e);
            // Do not throw an exception here. Returning null here will make
            // MetaModel attempt to count records manually and therefore recover
            // from the error.
            return null;
        }
    }

    @Override
    public DocumentSource getMixedDocumentSourceForSampling() {
        return null;
    }

    @Override
    public DocumentSource getDocumentSourceForTable(String sourceCollectionName) {
        return null;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy