blob: 0ac4ad56d2ee21771c763c629563f5df576c45f8 [file] [log] [blame]
/**
* 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
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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.atlas.web.resources;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Preconditions;
import org.apache.atlas.AtlasClient;
import org.apache.atlas.AtlasConfiguration;
import org.apache.atlas.classification.InterfaceAudience;
import org.apache.atlas.query.QueryParams;
import org.apache.atlas.utils.AtlasJson;
import org.apache.atlas.utils.AtlasPerfTracer;
import org.apache.atlas.utils.ParamChecker;
import org.apache.atlas.web.util.Servlets;
import org.apache.commons.configuration.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Jersey Resource for metadata operations.
*/
@Path("discovery")
@Singleton
@Service
@Deprecated
public class MetadataDiscoveryResource {
private static final Logger LOG = LoggerFactory.getLogger(MetadataDiscoveryResource.class);
private static final Logger PERF_LOG = AtlasPerfTracer.getPerfLogger("rest.MetadataDiscoveryResource");
private static final String QUERY_TYPE_DSL = "dsl";
private static final String QUERY_TYPE_GREMLIN = "gremlin";
private static final String QUERY_TYPE_FULLTEXT = "full-text";
private static final String LIMIT_OFFSET_DEFAULT = "-1";
private final boolean gremlinSearchEnabled;
private static Configuration applicationProperties = null;
private static final String ENABLE_GREMLIN_SEARCH_PROPERTY = "atlas.search.gremlin.enable";
/**
* Created by the Guice ServletModule and injected with the
* configured DiscoveryService.
*
* @param configuration configuration
*/
@Inject
public MetadataDiscoveryResource(Configuration configuration) {
applicationProperties = configuration;
gremlinSearchEnabled = applicationProperties != null && applicationProperties.getBoolean(ENABLE_GREMLIN_SEARCH_PROPERTY, false);
}
/**
* Search using a given query.
*
* @param query search query in DSL format falling back to full text.
* @param limit number of rows to be returned in the result, used for pagination. maxlimit > limit > 0. -1 maps to atlas.search.defaultlimit property value
* @param offset offset to the results returned, used for pagination. offset >= 0. -1 maps to offset 0
* @return JSON representing the type and results.
*/
@GET
@Path("search")
@Consumes(Servlets.JSON_MEDIA_TYPE)
@Produces(Servlets.JSON_MEDIA_TYPE)
public Response search(@QueryParam("query") String query,
@DefaultValue(LIMIT_OFFSET_DEFAULT) @QueryParam("limit") int limit,
@DefaultValue(LIMIT_OFFSET_DEFAULT) @QueryParam("offset") int offset) {
boolean dslQueryFailed = false;
Response response = null;
try {
response = searchUsingQueryDSL(query, limit, offset);
if (response.getStatus() != Response.Status.OK.getStatusCode()) {
dslQueryFailed = true;
}
} catch (Exception e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Error while running DSL. Switching to fulltext for query {}", query, e);
}
dslQueryFailed = true;
}
if ( dslQueryFailed ) {
response = searchUsingFullText(query, limit, offset);
}
return response;
}
/**
* Search using query DSL format.
*
* @param dslQuery search query in DSL format.
* @param limit number of rows to be returned in the result, used for pagination. maxlimit > limit > 0. -1 maps to atlas.search.defaultlimit property value
* @param offset offset to the results returned, used for pagination. offset >= 0. -1 maps to offset 0
* Limit and offset in API are used in conjunction with limit and offset in DSL query
* Final limit = min(API limit, max(query limit - API offset, 0))
* Final offset = API offset + query offset
*
* @return JSON representing the type and results.
*/
@GET
@Path("search/dsl")
@Consumes(Servlets.JSON_MEDIA_TYPE)
@Produces(Servlets.JSON_MEDIA_TYPE)
public Response searchUsingQueryDSL(@QueryParam("query") String dslQuery,
@DefaultValue(LIMIT_OFFSET_DEFAULT) @QueryParam("limit") int limit,
@DefaultValue(LIMIT_OFFSET_DEFAULT) @QueryParam("offset") int offset) {
if (LOG.isDebugEnabled()) {
LOG.debug("==> MetadataDiscoveryResource.searchUsingQueryDSL({}, {}, {})", dslQuery, limit, offset);
}
AtlasPerfTracer perf = null;
try {
if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) {
perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "MetadataDiscoveryResource.searchUsingQueryDSL(" + dslQuery + ", " + limit + ", " + offset + ")");
}
dslQuery = ParamChecker.notEmpty(dslQuery, "dslQuery cannot be null");
QueryParams queryParams = validateQueryParams(limit, offset);
final String jsonResultStr = ""; // TODO-typeSystem-removal: discoveryService.searchByDSL(dslQuery, queryParams);
ObjectNode response = new DSLJSONResponseBuilder().results(jsonResultStr).query(dslQuery).build();
return Response.ok(response).build();
} catch (IllegalArgumentException e) {
LOG.error("Unable to get entity list for dslQuery {}", dslQuery, e);
throw new WebApplicationException(Servlets.getErrorResponse(e, Response.Status.BAD_REQUEST));
} catch (WebApplicationException e) {
LOG.error("Unable to get entity list for dslQuery {}", dslQuery, e);
throw e;
} catch (Throwable e) {
LOG.error("Unable to get entity list for dslQuery {}", dslQuery, e);
throw new WebApplicationException(Servlets.getErrorResponse(e, Response.Status.INTERNAL_SERVER_ERROR));
} finally {
AtlasPerfTracer.log(perf);
if (LOG.isDebugEnabled()) {
LOG.debug("<== MetadataDiscoveryResource.searchUsingQueryDSL({}, {}, {})", dslQuery, limit, offset);
}
}
}
private QueryParams validateQueryParams(int limitParam, int offsetParam) {
int maxLimit = AtlasConfiguration.SEARCH_MAX_LIMIT.getInt();
int defaultLimit = AtlasConfiguration.SEARCH_DEFAULT_LIMIT.getInt();
int limit = defaultLimit;
boolean limitSet = (limitParam != Integer.valueOf(LIMIT_OFFSET_DEFAULT));
if (limitSet) {
ParamChecker.lessThan(limitParam, maxLimit, "limit");
ParamChecker.greaterThan(limitParam, 0, "limit");
limit = limitParam;
}
int offset = 0;
boolean offsetSet = (offsetParam != Integer.valueOf(LIMIT_OFFSET_DEFAULT));
if (offsetSet) {
ParamChecker.greaterThan(offsetParam, -1, "offset");
offset = offsetParam;
}
return new QueryParams(limit, offset);
}
/**
* Search using raw gremlin query format.
*
* @param gremlinQuery search query in raw gremlin format.
* @return JSON representing the type and results.
*/
@GET
@Path("search/gremlin")
@Consumes(Servlets.JSON_MEDIA_TYPE)
@Produces(Servlets.JSON_MEDIA_TYPE)
@InterfaceAudience.Private
public Response searchUsingGremlinQuery(@QueryParam("query") String gremlinQuery) {
if (LOG.isDebugEnabled()) {
LOG.debug("==> MetadataDiscoveryResource.searchUsingGremlinQuery({})", gremlinQuery);
}
AtlasPerfTracer perf = null;
try {
if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) {
perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "MetadataDiscoveryResource.searchUsingGremlinQuery(" + gremlinQuery + ")");
}
if (!gremlinSearchEnabled) {
throw new Exception("Gremlin search is not enabled.");
}
gremlinQuery = ParamChecker.notEmpty(gremlinQuery, "gremlinQuery cannot be null or empty");
final List<Map<String, String>> results = new ArrayList<>(); // TODO-typeSystem-removal: discoveryService.searchByGremlin(gremlinQuery);
ObjectNode response = AtlasJson.createV1ObjectNode();
response.put(AtlasClient.REQUEST_ID, Servlets.getRequestId());
response.put(AtlasClient.QUERY, gremlinQuery);
response.put(AtlasClient.QUERY_TYPE, QUERY_TYPE_GREMLIN);
response.putPOJO(AtlasClient.RESULTS, results);
response.put(AtlasClient.COUNT, results.size());
return Response.ok(response).build();
} catch (IllegalArgumentException e) {
LOG.error("Unable to get entity list for gremlinQuery {}", gremlinQuery, e);
throw new WebApplicationException(Servlets.getErrorResponse(e, Response.Status.BAD_REQUEST));
} catch (WebApplicationException e) {
LOG.error("Unable to get entity list for gremlinQuery {}", gremlinQuery, e);
throw e;
} catch (Throwable e) {
LOG.error("Unable to get entity list for gremlinQuery {}", gremlinQuery, e);
throw new WebApplicationException(Servlets.getErrorResponse(e, Response.Status.INTERNAL_SERVER_ERROR));
} finally {
AtlasPerfTracer.log(perf);
if (LOG.isDebugEnabled()) {
LOG.debug("<== MetadataDiscoveryResource.searchUsingGremlinQuery({})", gremlinQuery);
}
}
}
/**
* Search using full text search.
*
* @param query search query.
* @param limit number of rows to be returned in the result, used for pagination. maxlimit > limit > 0. -1 maps to atlas.search.defaultlimit property value
* @param offset offset to the results returned, used for pagination. offset >= 0. -1 maps to offset 0
* @return JSON representing the type and results.
*/
@GET
@Path("search/fulltext")
@Consumes(Servlets.JSON_MEDIA_TYPE)
@Produces(Servlets.JSON_MEDIA_TYPE)
public Response searchUsingFullText(@QueryParam("query") String query,
@DefaultValue(LIMIT_OFFSET_DEFAULT) @QueryParam("limit") int limit,
@DefaultValue(LIMIT_OFFSET_DEFAULT) @QueryParam("offset") int offset) {
if (LOG.isDebugEnabled()) {
LOG.debug("==> MetadataDiscoveryResource.searchUsingFullText({}, {}, {})", query, limit, offset);
}
AtlasPerfTracer perf = null;
try {
if (AtlasPerfTracer.isPerfTraceEnabled(PERF_LOG)) {
perf = AtlasPerfTracer.getPerfTracer(PERF_LOG, "MetadataDiscoveryResource.searchUsingFullText(" + query + ", " + limit + ", " + offset + ")");
}
query = ParamChecker.notEmpty(query, "query cannot be null or empty");
QueryParams queryParams = validateQueryParams(limit, offset);
final String jsonResultStr = ""; // TODO-typeSystem-removal: discoveryService.searchByFullText(query, queryParams);
ArrayNode rowsJsonArr = AtlasJson.parseToV1ArrayNode(jsonResultStr);
ObjectNode response = new FullTextJSonResponseBuilder().results(rowsJsonArr).query(query).build();
return Response.ok(response).build();
} catch (IllegalArgumentException e) {
LOG.error("Unable to get entity list for query {}", query, e);
throw new WebApplicationException(Servlets.getErrorResponse(e, Response.Status.BAD_REQUEST));
} catch (WebApplicationException e) {
LOG.error("Unable to get entity list for query {}", query, e);
throw e;
} catch (Throwable e) {
LOG.error("Unable to get entity list for query {}", query, e);
throw new WebApplicationException(Servlets.getErrorResponse(e, Response.Status.INTERNAL_SERVER_ERROR));
} finally {
AtlasPerfTracer.log(perf);
if (LOG.isDebugEnabled()) {
LOG.debug("<== MetadataDiscoveryResource.searchUsingFullText({}, {}, {})", query, limit, offset);
}
}
}
private class JsonResponseBuilder {
protected int count = 0;
protected String query;
protected String queryType;
protected ObjectNode response;
JsonResponseBuilder() {
this.response = AtlasJson.createV1ObjectNode();
}
protected JsonResponseBuilder count(int count) {
this.count = count;
return this;
}
public JsonResponseBuilder query(String query) {
this.query = query;
return this;
}
public JsonResponseBuilder queryType(String queryType) {
this.queryType = queryType;
return this;
}
protected ObjectNode build() {
Preconditions.checkNotNull(query, "Query cannot be null");
Preconditions.checkNotNull(queryType, "Query Type must be specified");
Preconditions.checkArgument(count >= 0, "Search Result count should be > 0");
response.put(AtlasClient.REQUEST_ID, Servlets.getRequestId());
response.put(AtlasClient.QUERY, query);
response.put(AtlasClient.QUERY_TYPE, queryType);
response.put(AtlasClient.COUNT, count);
return response;
}
}
private class DSLJSONResponseBuilder extends JsonResponseBuilder {
DSLJSONResponseBuilder() {
super();
}
private ArrayNode dslResults;
public DSLJSONResponseBuilder results(ArrayNode dslResults) {
this.dslResults = dslResults;
return this;
}
public DSLJSONResponseBuilder results(String dslResults) throws IOException {
return results(AtlasJson.parseToV1ArrayNode(dslResults));
}
@Override
public ObjectNode build() {
Preconditions.checkNotNull(dslResults);
count(dslResults.size());
queryType(QUERY_TYPE_DSL);
ObjectNode response = super.build();
response.putPOJO(AtlasClient.RESULTS, dslResults);
response.put(AtlasClient.DATATYPE, dslResults.get(AtlasClient.DATATYPE));
return response;
}
}
private class FullTextJSonResponseBuilder extends JsonResponseBuilder {
private ArrayNode fullTextResults;
public FullTextJSonResponseBuilder results(ArrayNode fullTextResults) {
this.fullTextResults = fullTextResults;
return this;
}
public FullTextJSonResponseBuilder results(String dslResults) throws IOException {
return results(AtlasJson.parseToV1ArrayNode(dslResults));
}
public FullTextJSonResponseBuilder() {
super();
}
@Override
public ObjectNode build() {
Preconditions.checkNotNull(fullTextResults);
count(fullTextResults.size());
queryType(QUERY_TYPE_FULLTEXT);
ObjectNode response = super.build();
response.put(AtlasClient.RESULTS, fullTextResults);
return response;
}
}
}