/* * 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 * * 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.streams.neo4j.http; import org.apache.streams.components.http.HttpConfiguration; import org.apache.streams.components.http.HttpProviderConfiguration; import org.apache.streams.components.http.provider.SimpleHttpProvider; import org.apache.streams.config.ComponentConfigurator; import org.apache.streams.config.StreamsConfigurator; import org.apache.streams.core.StreamsPersistReader; import org.apache.streams.core.StreamsResultSet; import org.apache.streams.graph.HttpGraphHelper; import org.apache.streams.graph.QueryGraphHelper; import org.apache.streams.neo4j.CypherQueryGraphHelper; import org.apache.streams.jackson.StreamsJacksonMapper; import org.apache.streams.neo4j.CypherQueryResponse; import org.apache.streams.neo4j.ItemData; import org.apache.streams.neo4j.ItemMetadata; import org.apache.streams.neo4j.Neo4jReaderConfiguration; import org.apache.streams.util.PropertyUtil; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.http.HttpEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.javatuples.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; /** * Reads a stream of activityobjects from vertices in a graph database with * an http rest endpoint (such as neo4j). */ public class Neo4jHttpPersistReader extends SimpleHttpProvider implements StreamsPersistReader { public static final String STREAMS_ID = Neo4jHttpPersistReader.class.getCanonicalName(); private static final Logger LOGGER = LoggerFactory.getLogger(Neo4jHttpPersistReader.class); private Neo4jReaderConfiguration config; private QueryGraphHelper queryGraphHelper; private HttpGraphHelper httpGraphHelper; private static ObjectMapper mapper = StreamsJacksonMapper.getInstance(); /** * GraphVertexReader constructor - resolve GraphReaderConfiguration from JVM 'graph'. */ public Neo4jHttpPersistReader() { this(new ComponentConfigurator<>(Neo4jReaderConfiguration.class).detectConfiguration(StreamsConfigurator.config.getConfig("neo4j"))); } /** * GraphVertexReader constructor - use supplied GraphReaderConfiguration. * @param configuration GraphReaderConfiguration */ public Neo4jHttpPersistReader(Neo4jReaderConfiguration configuration) { super((HttpProviderConfiguration)StreamsJacksonMapper.getInstance().convertValue(configuration, HttpProviderConfiguration.class).withHostname(configuration.getHosts().get(0))); super.configuration.setRequestMethod(HttpConfiguration.RequestMethod.POST); super.configuration.setResourcePath("/db"); super.configuration.setResource("data"); super.configuration.setResourcePostfix("cypher"); this.config = configuration; } /** * prepareHttpRequest * @param uri uri * @return result */ public HttpRequestBase prepareHttpRequest(URI uri) { HttpRequestBase baseRequest = super.prepareHttpRequest(uri); HttpPost post = (HttpPost) baseRequest; String query = config.getQuery(); Map<String, Object> params = mapper.convertValue(config.getParams(), Map.class); Pair<String, Map<String, Object>> queryPlusParams = new Pair(query, params); ObjectNode queryNode = httpGraphHelper.readData(queryPlusParams); try { String queryJsonString = mapper.writeValueAsString(queryNode); HttpEntity entity = new StringEntity(queryJsonString, ContentType.create("application/json")); post.setEntity(entity); } catch (JsonProcessingException ex) { LOGGER.error("JsonProcessingException", ex); return null; } return post; } /** * Neo API query returns something like this: * { "columns": [ "v" ], "data": [ [ { "data": { props }, etc... } ], [ { "data": { props }, etc... } ] ] } * * @param jsonNode jsonNode * @return result */ public List<ObjectNode> parse(JsonNode jsonNode) { List<ObjectNode> results = new ArrayList<>(); ObjectNode root = (ObjectNode) jsonNode; CypherQueryResponse cypherQueryResponse = mapper.convertValue(root, CypherQueryResponse.class); for ( List<List<ItemMetadata>> dataWrapper : cypherQueryResponse.getData()) { for (List<ItemMetadata> itemMetadatas : dataWrapper) { for (ItemMetadata itemMetadata : itemMetadatas) { ItemData itemData = itemMetadata.getData(); LOGGER.debug("itemData: " + itemData); results.add(PropertyUtil.unflattenMap(itemData.getAdditionalProperties(), '.')); } } } return results; } @Override public String getId() { return STREAMS_ID; } @Override public void prepare(Object configurationObject) { super.prepare(configurationObject); mapper = StreamsJacksonMapper.getInstance(); queryGraphHelper = new CypherQueryGraphHelper(); httpGraphHelper = new Neo4jHttpGraphHelper(); Objects.requireNonNull(queryGraphHelper); Objects.requireNonNull(httpGraphHelper); } @Override public StreamsResultSet readAll() { return readCurrent(); } }