Commit 9d01934b by Venkatesh Seetharam

BUG-32591 Integrate Search DSL with Graph Repository. Contributed by Venkatesh Seetharam

parent 8e5d4827
......@@ -309,12 +309,6 @@
<version>${falcon.version}</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.9</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
......@@ -701,9 +695,7 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>2.7.2</version>
<configuration>
<systemPropertyVariables>
<tapestry.execution-mode>Qa</tapestry.execution-mode>
</systemPropertyVariables>
<skipTests>true</skipTests>
</configuration>
</plugin>
......
......@@ -16,15 +16,8 @@
* limitations under the License.
*/
/*
* Created by IntelliJ IDEA.
* User: seetharam
* Date: 12/1/14
* Time: 2:21 PM
*/
package org.apache.hadoop.metadata;
import com.google.inject.Scopes;
import com.google.inject.throwingproviders.ThrowingProviderBinder;
import com.thinkaurelius.titan.core.TitanGraph;
......
/**
* 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.hadoop.metadata.discovery;
import org.apache.hadoop.metadata.MetadataException;
import java.security.PrivilegedActionException;
public class DiscoveryException extends MetadataException {
/**
* Constructs a new exception with the specified detail message. The
* cause is not initialized, and may subsequently be initialized by
* a call to {@link #initCause}.
*
* @param message the detail message. The detail message is saved for
* later retrieval by the {@link #getMessage()} method.
*/
public DiscoveryException(String message) {
super(message);
}
/**
* Constructs a new exception with the specified detail message and
* cause. <p>Note that the detail message associated with
* {@code cause} is <i>not</i> automatically incorporated in
* this exception's detail message.
*
* @param message the detail message (which is saved for later retrieval
* by the {@link #getMessage()} method).
* @param cause the cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A <tt>null</tt> value is
* permitted, and indicates that the cause is nonexistent or
* unknown.)
* @since 1.4
*/
public DiscoveryException(String message, Throwable cause) {
super(message, cause);
}
/**
* Constructs a new exception with the specified cause and a detail
* message of <tt>(cause==null ? null : cause.toString())</tt> (which
* typically contains the class and detail message of <tt>cause</tt>).
* This constructor is useful for exceptions that are little more than
* wrappers for other throwables (for example, {@link
* PrivilegedActionException}).
*
* @param cause the cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A <tt>null</tt> value is
* permitted, and indicates that the cause is nonexistent or
* unknown.)
* @since 1.4
*/
public DiscoveryException(Throwable cause) {
super(cause);
}
}
......@@ -18,7 +18,6 @@
package org.apache.hadoop.metadata.discovery;
import org.apache.hadoop.metadata.MetadataException;
import org.codehaus.jettison.json.JSONObject;
import java.util.HashMap;
......@@ -32,28 +31,38 @@ import java.util.Set;
public interface DiscoveryService {
/**
* Search using query DSL.
*
* @param dslQuery query in DSL format.
* @return JSON representing the type and results.
*/
String searchByDSL(String dslQuery) throws DiscoveryException;
/**
* Assumes the User is familiar with the persistence structure of the Repository.
* The given query is run uninterpreted against the underlying Graph Store.
* The results are returned as a List of Rows. each row is a Map of Key,Value pairs.
*
* @param gremlinQuery query in gremlin dsl format
* @return List of Maps
* @throws org.apache.hadoop.metadata.MetadataException
* @throws org.apache.hadoop.metadata.discovery.DiscoveryException
*/
List<Map<String,String>> searchByGremlin(String gremlinQuery) throws MetadataException;
List<Map<String,String>> searchByGremlin(String gremlinQuery) throws DiscoveryException;
/**
* Simple direct graph search and depth traversal.
* @param searchText is plain text
* @param prop is the Vertex property to search.
*/
Map<String, HashMap<String,JSONObject>> textSearch(String searchText, int depth, String prop);
Map<String, HashMap<String,JSONObject>> textSearch(String searchText,
int depth, String prop);
/**
* Simple graph walker for search interface, which allows following of specific edges only.
* @param edgesToFollow is a comma-separated-list of edges to follow.
*/
Map<String, HashMap<String,JSONObject>> relationshipWalk(String guid, int depth, String edgesToFollow);
Map<String, HashMap<String,JSONObject>> relationshipWalk(String guid,
int depth, String edgesToFollow);
/**
* Return a Set of indexed properties in the graph.
......
......@@ -30,6 +30,8 @@ import java.util.List;
*/
public interface MetadataRepository extends Service {
String getTypeAttributeName();
String createEntity(IReferenceableInstance entity,
String entityType) throws RepositoryException;
......
......@@ -18,33 +18,35 @@
package org.apache.hadoop.metadata.repository.graph;
final class Constants {
public final class Constants {
private Constants() {
}
static final String GUID_PROPERTY_KEY = "guid";
static final String GUID_INDEX = "guid_index";
public static final String GUID_PROPERTY_KEY = "guid";
public static final String GUID_INDEX = "guid_index";
static final String ENTITY_TYPE_PROPERTY_KEY = "type";
static final String ENTITY_TYPE_INDEX = "type_index";
public static final String ENTITY_TYPE_PROPERTY_KEY = "type";
public static final String ENTITY_TYPE_INDEX = "type_index";
static final String VERSION_PROPERTY_KEY = "version";
static final String TIMESTAMP_PROPERTY_KEY = "timestamp";
public static final String VERSION_PROPERTY_KEY = "version";
public static final String TIMESTAMP_PROPERTY_KEY = "timestamp";
public static final String TRAIT_NAMES_PROPERTY_KEY = "traits";
/**
* search backing index name.
*/
static final String BACKING_INDEX = "search";
static final String INDEX_NAME = "metadata";
public static final String BACKING_INDEX = "search";
public static final String INDEX_NAME = "metadata";
/**
* search backing index name for vertex keys.
*/
static final String VERTEX_INDEX = "vertex_index";
public static final String VERTEX_INDEX = "vertex_index";
/**
* search backing index name for edge labels.
*/
static final String EDGE_INDEX = "edge_index";
public static final String EDGE_INDEX = "edge_index";
}
......@@ -116,7 +116,8 @@ public class GraphBackedSearchIndexer implements SearchIndexer {
case ENUM:
case ARRAY:
case MAP:
// do nothing since these are NOT types
// do nothing since these are only attributes
// and not types like structs, traits or classes
break;
case STRUCT:
......
/**
* 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.hadoop.metadata.search;
import com.thinkaurelius.titan.core.TitanVertex;
import org.apache.hadoop.metadata.ITypedReferenceableInstance;
import org.apache.hadoop.metadata.ITypedStruct;
import org.apache.hadoop.metadata.MetadataException;
import org.apache.hadoop.metadata.query.Expressions;
import org.apache.hadoop.metadata.query.GraphPersistenceStrategies;
import org.apache.hadoop.metadata.query.TypeUtils;
import org.apache.hadoop.metadata.repository.MetadataRepository;
import org.apache.hadoop.metadata.repository.graph.Constants;
import org.apache.hadoop.metadata.repository.graph.GraphBackedMetadataRepository;
import org.apache.hadoop.metadata.storage.Id;
import org.apache.hadoop.metadata.types.AttributeInfo;
import org.apache.hadoop.metadata.types.IDataType;
import org.apache.hadoop.metadata.types.Multiplicity;
import org.apache.hadoop.metadata.types.StructType;
import org.apache.hadoop.metadata.types.TraitType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* Default implementation of GraphPersistenceStrategy.
*/
public class DefaultGraphPersistenceStrategy implements GraphPersistenceStrategies {
private static final Logger LOG = LoggerFactory.getLogger(DefaultGraphPersistenceStrategy.class);
private final GraphBackedMetadataRepository metadataRepository;
public DefaultGraphPersistenceStrategy(MetadataRepository metadataRepository) {
this.metadataRepository = (GraphBackedMetadataRepository) metadataRepository;
}
@Override
public String typeAttributeName() {
return metadataRepository.getTypeAttributeName();
}
@Override
public String edgeLabel(IDataType<?> dataType, AttributeInfo aInfo) {
return metadataRepository.getEdgeLabel(dataType, aInfo);
}
@Override
public String traitLabel(IDataType<?> dataType, String traitName) {
return metadataRepository.getTraitLabel(dataType, traitName);
}
@Override
public String fieldNameInVertex(IDataType<?> dataType, AttributeInfo aInfo) {
return metadataRepository.getFieldNameInVertex(dataType, aInfo);
}
@Override
public List<String> traitNames(TitanVertex vertex) {
return metadataRepository.getTraitNames(vertex);
}
@Override
public String fieldPrefixInSelect() {
return "it";
}
@Override
public Id getIdFromVertex(String dataTypeName, TitanVertex vertex) {
return metadataRepository.getIdFromVertex(dataTypeName, vertex);
}
@Override
public <U> U constructInstance(IDataType<U> dataType, Object value) {
try {
switch (dataType.getTypeCategory()) {
case PRIMITIVE:
case ENUM:
return dataType.convert(value, Multiplicity.OPTIONAL);
case ARRAY:
// todo
break;
case MAP:
// todo
break;
case STRUCT:
TitanVertex structVertex = (TitanVertex) value;
StructType structType = (StructType) dataType;
ITypedStruct structInstance = structType.createInstance();
metadataRepository.getGraphToInstanceMapper().mapVertexToInstance(
structVertex, structInstance, structType.fieldMapping().fields);
return dataType.convert(structInstance, Multiplicity.OPTIONAL);
case TRAIT:
TitanVertex traitVertex = (TitanVertex) value;
TraitType traitType = (TraitType) dataType;
ITypedStruct traitInstance = traitType.createInstance();
// todo - this is not right, we should load the Instance associated with this
// trait. for now just loading the trait struct.
// metadataRepository.getGraphToInstanceMapper().mapVertexToTraitInstance(
// traitVertex, dataType.getName(), , traitType, traitInstance);
metadataRepository.getGraphToInstanceMapper().mapVertexToInstance(
traitVertex, traitInstance, traitType.fieldMapping().fields);
break;
case CLASS:
TitanVertex classVertex = (TitanVertex) value;
ITypedReferenceableInstance classInstance =
metadataRepository.getGraphToInstanceMapper().mapGraphToTypedInstance(
classVertex.<String>getProperty(Constants.GUID_PROPERTY_KEY),
classVertex);
return dataType.convert(classInstance, Multiplicity.OPTIONAL);
default:
throw new UnsupportedOperationException(
"Load for type " + dataType + "is not supported");
}
} catch (MetadataException e) {
LOG.error("error while constructing an instance", e);
}
return null;
}
@Override
public String edgeLabel(TypeUtils.FieldInfo fInfo) {
return fInfo.reverseDataType() == null
? edgeLabel(fInfo.dataType(), fInfo.attrInfo())
: edgeLabel(fInfo.reverseDataType(), fInfo.attrInfo());
}
@Override
public String gremlinCompOp(Expressions.ComparisonExpression op) {
switch (op.symbol()) {
case "=":
return "T.eq";
case "!=":
return "T.neq";
case ">":
return "T.gt";
case ">=":
return "T.gte";
case "<":
return "T.lt";
case "<=":
return "T.lte";
default:
throw new RuntimeException(("Comparison operator not supported in Gremlin: " + op));
}
}
@Override
public String loopObjectExpression(IDataType<?> dataType) {
return "{it.object." + typeAttributeName() + " == '" + dataType.getName() + "'}";
}
}
......@@ -26,8 +26,11 @@ import org.apache.hadoop.metadata.repository.graph.GraphBackedMetadataRepository
import org.apache.hadoop.metadata.types.ClassType;
import org.apache.hadoop.metadata.types.Multiplicity;
import org.apache.hadoop.metadata.types.TypeSystem;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONObject;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
......@@ -36,32 +39,66 @@ import javax.inject.Inject;
@Guice(modules = RepositoryMetadataModule.class)
public class GraphBackedDiscoveryServiceTest {
private TypeSystem typeSystem;
@Inject
private GraphBackedMetadataRepository repositoryService;
@Inject
private GraphBackedDiscoveryService discoveryService;
@BeforeMethod
@BeforeClass
public void setUp() throws Exception {
typeSystem = TypeSystem.getInstance();
}
@AfterMethod
public void tearDown() throws Exception {
TypeSystem typeSystem = TypeSystem.getInstance();
typeSystem.reset();
}
TestUtils.defineDeptEmployeeTypes(typeSystem);
@Test
public void testRawSearch1() throws Exception {
Referenceable hrDept = TestUtils.createDeptEg1(typeSystem);
ClassType deptType = typeSystem.getDataType(ClassType.class, "Department");
ITypedReferenceableInstance hrDept2 = deptType.convert(hrDept, Multiplicity.REQUIRED);
repositoryService.createEntity(hrDept2, "Department");
}
@AfterClass
public void tearDown() throws Exception {
TypeSystem.getInstance().reset();
}
@Test
public void testSearchByDSL() throws Exception {
String dslQuery = "from Department";
String jsonResults = discoveryService.searchByDSL(dslQuery);
Assert.assertNotNull(jsonResults);
JSONObject results = new JSONObject(jsonResults);
Assert.assertEquals(results.length(), 3);
System.out.println("results = " + results);
Object query = results.get("query");
Assert.assertNotNull(query);
JSONObject dataType = results.getJSONObject("dataType");
Assert.assertNotNull(dataType);
String typeName = dataType.getString("typeName");
Assert.assertNotNull(typeName);
Assert.assertEquals(typeName, "Department");
JSONArray rows = results.getJSONArray("rows");
Assert.assertNotNull(rows);
Assert.assertEquals(rows.length(), 1);
}
@Test (expectedExceptions = Throwable.class)
public void testSearchByDSLBadQuery() throws Exception {
String dslQuery = "from blah";
discoveryService.searchByDSL(dslQuery);
Assert.fail();
}
@Test
public void testRawSearch1() throws Exception {
// Query for all Vertices in Graph
Object r = discoveryService.searchByGremlin("g.V.toList()");
System.out.println("search result = " + r);
......@@ -74,14 +111,4 @@ public class GraphBackedDiscoveryServiceTest {
r = discoveryService.searchByGremlin("g.V.filter{it.typeName == 'Person'}.'Person.name'.toList()");
System.out.println("search result = " + r);
}
@Test
public void testTextSearch() throws Exception {
}
@Test
public void testRelationshipWalk() throws Exception {
}
}
\ No newline at end of file
......@@ -19,5 +19,7 @@
storage.backend=inmemory
# Graph Search Index
index.search.backend=lucene
index.search.directory=target/data/es
\ No newline at end of file
index.search.backend=elasticsearch
index.search.directory=target/data/es
index.search.elasticsearch.client-only=false
index.search.elasticsearch.local-mode=true
......@@ -39,7 +39,6 @@ import org.apache.hadoop.metadata.types.Multiplicity;
import org.apache.hadoop.metadata.types.StructTypeDefinition;
import org.apache.hadoop.metadata.types.TraitType;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -304,26 +303,6 @@ public class EntityJerseyResourceIT extends BaseResourceIT {
sumbitType(typesAsJSON, TABLE_TYPE);
}
private void sumbitType(String typesAsJSON, String type) throws JSONException {
WebResource resource = service
.path("api/metadata/types/submit")
.path(type);
ClientResponse clientResponse = resource
.accept(MediaType.APPLICATION_JSON)
.type(MediaType.APPLICATION_JSON)
.method(HttpMethod.POST, ClientResponse.class, typesAsJSON);
Assert.assertEquals(clientResponse.getStatus(), Response.Status.OK.getStatusCode());
String responseAsString = clientResponse.getEntity(String.class);
Assert.assertNotNull(responseAsString);
JSONObject response = new JSONObject(responseAsString);
Assert.assertEquals(response.get("typeName"), type);
Assert.assertNotNull(response.get("types"));
Assert.assertNotNull(response.get("requestId"));
}
private ITypedReferenceableInstance createHiveTableInstance() throws Exception {
Referenceable databaseInstance = new Referenceable(DATABASE_TYPE);
databaseInstance.set("name", DATABASE_NAME);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment