http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/catalog/src/main/java/org/apache/atlas/catalog/query/BaseQueryExpression.java ---------------------------------------------------------------------- diff --git a/catalog/src/main/java/org/apache/atlas/catalog/query/BaseQueryExpression.java b/catalog/src/main/java/org/apache/atlas/catalog/query/BaseQueryExpression.java new file mode 100644 index 0000000..2364ee5 --- /dev/null +++ b/catalog/src/main/java/org/apache/atlas/catalog/query/BaseQueryExpression.java @@ -0,0 +1,105 @@ +/** + * 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.catalog.query; + +import com.tinkerpop.blueprints.Vertex; +import com.tinkerpop.pipes.Pipe; +import com.tinkerpop.pipes.PipeFunction; +import com.tinkerpop.pipes.filter.FilterFunctionPipe; +import org.apache.atlas.catalog.VertexWrapper; +import org.apache.atlas.catalog.definition.ResourceDefinition; + +import java.util.Collection; +import java.util.HashSet; + +/** + * Base query expression class. + */ +public abstract class BaseQueryExpression implements QueryExpression { + protected String m_field; + protected final String m_expectedValue; + protected final ResourceDefinition resourceDefinition; + protected boolean negate = false; + protected Collection<String> properties = new HashSet<>(); + + protected BaseQueryExpression(String field, String expectedValue, ResourceDefinition resourceDefinition) { + m_field = field; + if (field != null) { + properties.add(field); + } + m_expectedValue = expectedValue; + this.resourceDefinition = resourceDefinition; + } + + @Override + public boolean evaluate(VertexWrapper vWrapper) { + return negate ^ evaluate(vWrapper.getProperty(m_field)); + } + + @Override + public Collection<String> getProperties() { + return properties; + } + + @Override + public boolean evaluate(Object value) { + // subclasses which don't override evaluate(VertexWrapper) should implement this + return false; + } + + //todo: use 'has' instead of closure where possible for performance + public Pipe asPipe() { + return new FilterFunctionPipe(new PipeFunction<Vertex, Boolean>() { + @Override + public Boolean compute(Vertex vertex) { + return evaluate(new VertexWrapper(vertex, resourceDefinition)); + } + }); + } + + @Override + public String getField() { + return m_field; + } + + @Override + public String getExpectedValue() { + return m_expectedValue; + } + + @Override + public void setField(String field) { + m_field = field; + } + + @Override + public void setNegate() { + this.negate = true; + } + + @Override + public boolean isNegate() { + return negate; + } + + @Override + public boolean isProjectionExpression() { + return getField() != null && getField().contains(QueryFactory.PATH_SEP_TOKEN); + } +}
http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/catalog/src/main/java/org/apache/atlas/catalog/query/BooleanQueryExpression.java ---------------------------------------------------------------------- diff --git a/catalog/src/main/java/org/apache/atlas/catalog/query/BooleanQueryExpression.java b/catalog/src/main/java/org/apache/atlas/catalog/query/BooleanQueryExpression.java new file mode 100644 index 0000000..b4d759a --- /dev/null +++ b/catalog/src/main/java/org/apache/atlas/catalog/query/BooleanQueryExpression.java @@ -0,0 +1,141 @@ +/** + * 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.catalog.query; + +import com.tinkerpop.pipes.Pipe; +import com.tinkerpop.pipes.filter.AndFilterPipe; +import com.tinkerpop.pipes.filter.OrFilterPipe; +import org.apache.atlas.catalog.definition.ResourceDefinition; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanQuery; + +import java.util.*; + +/** + * Expression where operands are other expressions and operator is logical AND or OR + */ +public class BooleanQueryExpression extends BaseQueryExpression { + private final BooleanClause[] clauses; + private final QueryFactory queryFactory; + + public BooleanQueryExpression(BooleanQuery query, ResourceDefinition resourceDefinition, QueryFactory queryFactory) { + super(null, null, resourceDefinition); + clauses = query.getClauses(); + this.queryFactory = queryFactory; + } + + @Override + public Pipe asPipe() { + Map<BooleanClause.Occur, Collection<BooleanClause>> groupedClauses = groupClauses(); + + Pipe andPipe = null; + Collection<Pipe> andPipes = processAndClauses(groupedClauses); + andPipes.addAll(processNotClauses(groupedClauses)); + if (! andPipes.isEmpty()) { + andPipe = new AndFilterPipe(andPipes.toArray(new Pipe[andPipes.size()])); + } + + Collection<Pipe> orPipes = processOrClauses(groupedClauses); + if (! orPipes.isEmpty()) { + if (andPipe != null) { + orPipes.add(andPipe); + } + return new OrFilterPipe(orPipes.toArray(new Pipe[orPipes.size()])); + } else { + return andPipe; + } + } + + private Map<BooleanClause.Occur, Collection<BooleanClause>> groupClauses() { + Map<BooleanClause.Occur, Collection<BooleanClause>> groupedClauses = new HashMap<>(); + for (BooleanClause clause : clauses) { + BooleanClause.Occur occur = resolveClauseOccur(clause); + Collection<BooleanClause> clauseGrouping = groupedClauses.get(occur); + if (clauseGrouping == null) { + clauseGrouping = new ArrayList<>(); + groupedClauses.put(occur, clauseGrouping); + } + clauseGrouping.add(clause); + } + return groupedClauses; + } + + private BooleanClause.Occur resolveClauseOccur(BooleanClause clause) { + BooleanClause.Occur occur = clause.getOccur(); + if (negate) { + switch (occur) { + case SHOULD: + occur = BooleanClause.Occur.MUST_NOT; + break; + case MUST: + occur = BooleanClause.Occur.SHOULD; + break; + case MUST_NOT: + occur = BooleanClause.Occur.SHOULD; + break; + } + } + return occur; + } + + private Collection<Pipe> processAndClauses(Map<BooleanClause.Occur, Collection<BooleanClause>> groupedClauses) { + Collection<BooleanClause> andClauses = groupedClauses.get(BooleanClause.Occur.MUST); + Collection<Pipe> andPipes = new ArrayList<>(); + if (andClauses != null) { + for (BooleanClause andClause : andClauses) { + QueryExpression queryExpression = queryFactory.create(andClause.getQuery(), resourceDefinition); + properties.addAll(queryExpression.getProperties()); + andPipes.add(queryExpression.asPipe()); + } + } + return andPipes; + } + + + private Collection<Pipe> processOrClauses(Map<BooleanClause.Occur, Collection<BooleanClause>> groupedClauses) { + Collection<BooleanClause> shouldClauses = groupedClauses.get(BooleanClause.Occur.SHOULD); + Collection<Pipe> orPipes = new ArrayList<>(); + if (shouldClauses != null) { + for (BooleanClause shouldClause : shouldClauses) { + QueryExpression queryExpression = queryFactory.create(shouldClause.getQuery(), resourceDefinition); + // don't negate expression if we negated MUST_NOT -> SHOULD + if (negate && shouldClause.getOccur() != BooleanClause.Occur.MUST_NOT) { + queryExpression.setNegate(); + } + properties.addAll(queryExpression.getProperties()); + orPipes.add(queryExpression.asPipe()); + } + } + return orPipes; + } + + private Collection<Pipe> processNotClauses(Map<BooleanClause.Occur, Collection<BooleanClause>> groupedClauses) { + Collection<BooleanClause> notClauses = groupedClauses.get(BooleanClause.Occur.MUST_NOT); + Collection<Pipe> notPipes = new ArrayList<>(); + if (notClauses != null) { + for (BooleanClause notClause : notClauses) { + QueryExpression queryExpression = queryFactory.create(notClause.getQuery(), resourceDefinition); + queryExpression.setNegate(); + properties.addAll(queryExpression.getProperties()); + notPipes.add(queryExpression.asPipe()); + } + } + return notPipes; + } +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/catalog/src/main/java/org/apache/atlas/catalog/query/PrefixQueryExpression.java ---------------------------------------------------------------------- diff --git a/catalog/src/main/java/org/apache/atlas/catalog/query/PrefixQueryExpression.java b/catalog/src/main/java/org/apache/atlas/catalog/query/PrefixQueryExpression.java new file mode 100644 index 0000000..6b43667 --- /dev/null +++ b/catalog/src/main/java/org/apache/atlas/catalog/query/PrefixQueryExpression.java @@ -0,0 +1,39 @@ +/** + * 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.catalog.query; + +import org.apache.atlas.catalog.definition.ResourceDefinition; +import org.apache.lucene.search.PrefixQuery; + +/** + * Expression that evaluates whether a property starts with a prefix. + */ +public class PrefixQueryExpression extends BaseQueryExpression { + + // query 'f*' results in a PrefixQuery + public PrefixQueryExpression(PrefixQuery query, ResourceDefinition resourceDefinition) { + super(query.getPrefix().field(), query.getPrefix().text(), resourceDefinition); + } + + @Override + public boolean evaluate(Object value) { + return value != null && String.valueOf(value).startsWith(getExpectedValue()); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/catalog/src/main/java/org/apache/atlas/catalog/query/ProjectionQueryExpression.java ---------------------------------------------------------------------- diff --git a/catalog/src/main/java/org/apache/atlas/catalog/query/ProjectionQueryExpression.java b/catalog/src/main/java/org/apache/atlas/catalog/query/ProjectionQueryExpression.java new file mode 100644 index 0000000..b915877 --- /dev/null +++ b/catalog/src/main/java/org/apache/atlas/catalog/query/ProjectionQueryExpression.java @@ -0,0 +1,122 @@ +/** + * 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.catalog.query; + +import com.thinkaurelius.titan.core.attribute.Text; +import com.tinkerpop.gremlin.java.GremlinPipeline; +import com.tinkerpop.pipes.Pipe; +import com.tinkerpop.pipes.PipeFunction; +import com.tinkerpop.pipes.filter.FilterFunctionPipe; +import org.apache.atlas.catalog.VertexWrapper; +import org.apache.atlas.catalog.definition.ResourceDefinition; +import org.apache.atlas.catalog.projection.ProjectionResult; +import org.apache.atlas.catalog.projection.Relation; + +import java.util.*; + +/** + * Query expression wrapper which handles projection queries. + */ +public class ProjectionQueryExpression extends BaseQueryExpression { + + private final QueryExpression underlyingExpression; + private final ResourceDefinition resourceDefinition; + + private final String[] fieldSegments; + + protected ProjectionQueryExpression(QueryExpression underlyingExpression, ResourceDefinition resourceDefinition) { + super(underlyingExpression.getField(), underlyingExpression.getExpectedValue(), resourceDefinition); + + this.underlyingExpression = underlyingExpression; + this.resourceDefinition = resourceDefinition; + this.fieldSegments = getField().split(QueryFactory.PATH_SEP_TOKEN); + } + + @Override + public Pipe asPipe() { + //todo: encapsulate all of this path logic including path sep escaping and normalizing + final int sepIdx = getField().indexOf(QueryFactory.PATH_SEP_TOKEN); + final String edgeToken = getField().substring(0, sepIdx); + GremlinPipeline pipeline = new GremlinPipeline(); + + Relation relation = resourceDefinition.getRelations().get(fieldSegments[0]); + if (relation != null) { + pipeline = pipeline.outE(); + pipeline.add(relation.asPipe()).inV(); + } else { + if (resourceDefinition.getProjections().get(fieldSegments[0]) != null) { + return super.asPipe(); + } else { + //todo: default Relation implementation + pipeline = pipeline.outE().has("label", Text.REGEX, String.format(".*\\.%s", edgeToken)).inV(); + } + } + //todo: set resource definition from relation on underlying expression where appropriate + String childFieldName = getField().substring(sepIdx + QueryFactory.PATH_SEP_TOKEN.length()); + underlyingExpression.setField(childFieldName); + + Pipe childPipe; + if (childFieldName.contains(QueryFactory.PATH_SEP_TOKEN)) { + childPipe = new ProjectionQueryExpression(underlyingExpression, resourceDefinition).asPipe(); + } else { + childPipe = underlyingExpression.asPipe(); + } + pipeline.add(childPipe); + + return negate ? new FilterFunctionPipe(new ExcludePipeFunction(pipeline)) : pipeline; + } + + @Override + public boolean evaluate(VertexWrapper vWrapper) { + boolean result = false; + Iterator<ProjectionResult> projectionIterator = resourceDefinition.getProjections(). + get(fieldSegments[0]).values(vWrapper).iterator(); + + while (! result && projectionIterator.hasNext()) { + ProjectionResult projectionResult = projectionIterator.next(); + for (Map<String, Object> propertyMap : projectionResult.getPropertyMaps()) { + Object val = propertyMap.get(fieldSegments[1]); + if (val != null && underlyingExpression.evaluate(QueryFactory.escape(val))) { + result = true; + break; + } + } + } + return negate ^ result; + } + + private static class ExcludePipeFunction implements PipeFunction<Object, Boolean> { + private final GremlinPipeline excludePipeline; + + public ExcludePipeFunction(GremlinPipeline excludePipeline) { + this.excludePipeline = excludePipeline; + } + + @Override + public Boolean compute(Object vertices) { + GremlinPipeline p = new GremlinPipeline(Collections.singleton(vertices)); + p.add(excludePipeline); + return p.gather().toList().isEmpty(); + } + } + + protected QueryExpression getUnderlyingExpression() { + return underlyingExpression; + } +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/catalog/src/main/java/org/apache/atlas/catalog/query/QueryExpression.java ---------------------------------------------------------------------- diff --git a/catalog/src/main/java/org/apache/atlas/catalog/query/QueryExpression.java b/catalog/src/main/java/org/apache/atlas/catalog/query/QueryExpression.java new file mode 100644 index 0000000..78436c0 --- /dev/null +++ b/catalog/src/main/java/org/apache/atlas/catalog/query/QueryExpression.java @@ -0,0 +1,99 @@ +/** + * 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.catalog.query; + +import com.tinkerpop.pipes.Pipe; +import org.apache.atlas.catalog.VertexWrapper; + +import java.util.Collection; + +/** + * Represents a query expression. + */ +public interface QueryExpression { + /** + * Evaluate the expression based on properties of the provied vertex. + * + * @param vWrapper vertex wrapper that expression is applied to + * @return result of expression evaluation + */ + boolean evaluate(VertexWrapper vWrapper); + + /** + * Evaluate the expression based on the provided value. + * + * @param value value used to evaluate expression + * @return + */ + boolean evaluate(Object value); + + /** + * Get the complete set of properties which are contained in the expression. + * + * @return collection of expression properties + */ + Collection<String> getProperties(); + + /** + * Get the pipe representation of the expression. + * + * @return pipe representation + */ + Pipe asPipe(); + + /** + * Negate the expression. + */ + void setNegate(); + + /** + * Get the negate status of the expression. + * + * @return true if the expression is negated, false otherwise + */ + boolean isNegate(); + + /** + * Determine whether the expression is being applied to a projection. + * + * @return true if expression is being applied to a projection, false otherwise + */ + boolean isProjectionExpression(); + + /** + * Get the field name used in the expression. + * + * @return expression field name or null if there is no field name + */ + String getField(); + + /** + * Set the expressions field name. + * + * @param fieldName field name + */ + public void setField(String fieldName); + + /** + * Get the expected value for the expression. + * + * @return expected value or null if there isn't a expected value + */ + String getExpectedValue(); +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/catalog/src/main/java/org/apache/atlas/catalog/query/QueryFactory.java ---------------------------------------------------------------------- diff --git a/catalog/src/main/java/org/apache/atlas/catalog/query/QueryFactory.java b/catalog/src/main/java/org/apache/atlas/catalog/query/QueryFactory.java new file mode 100644 index 0000000..39ce11a --- /dev/null +++ b/catalog/src/main/java/org/apache/atlas/catalog/query/QueryFactory.java @@ -0,0 +1,182 @@ +/** + * 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.catalog.query; + +import org.apache.atlas.catalog.Request; +import org.apache.atlas.catalog.TermPath; +import org.apache.atlas.catalog.definition.*; +import org.apache.atlas.catalog.exception.CatalogRuntimeException; +import org.apache.atlas.catalog.exception.InvalidQueryException; +import org.apache.lucene.analysis.core.KeywordAnalyzer; +import org.apache.lucene.queryparser.classic.ParseException; +import org.apache.lucene.queryparser.classic.QueryParser; +import org.apache.lucene.sandbox.queries.regex.RegexQuery; +import org.apache.lucene.search.*; +import org.apache.lucene.util.Version; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + +/** + * Factory used to create QueryAdapter instances. + */ +public class QueryFactory { + private static final Logger LOG = LoggerFactory.getLogger(QueryFactory.class); + public static final String PATH_SEP_TOKEN = "__slash__"; + + private final Map<Class<? extends Query>, ExpressionCreateFunction<? extends Query>> + expressionCreateFunctions = new HashMap<>(); + + public QueryFactory() { + registerExpressionCreateFunctions(); + } + + public AtlasQuery createTaxonomyQuery(Request request) throws InvalidQueryException { + ResourceDefinition taxonomyDefinition = new TaxonomyResourceDefinition(); + QueryExpression queryExpression = create(request, taxonomyDefinition); + return new AtlasTaxonomyQuery(queryExpression, taxonomyDefinition, request); + } + + public AtlasQuery createTermQuery(Request request) throws InvalidQueryException { + ResourceDefinition termDefinition = new TermResourceDefinition(); + QueryExpression queryExpression = create(request, termDefinition); + TermPath termPath = request.getProperty("termPath"); + return new AtlasTermQuery(queryExpression, termDefinition, termPath, request); + } + + public AtlasQuery createEntityQuery(Request request) throws InvalidQueryException { + ResourceDefinition entityDefinition = new EntityResourceDefinition(); + QueryExpression queryExpression = create(request, entityDefinition); + return new AtlasEntityQuery(queryExpression, entityDefinition, request); + } + + public AtlasQuery createEntityTagQuery(Request request) throws InvalidQueryException { + ResourceDefinition entityTagDefinition = new EntityTagResourceDefinition(); + QueryExpression queryExpression = create(request, entityTagDefinition); + String guid = request.getProperty("id"); + return new AtlasEntityTagQuery(queryExpression, entityTagDefinition, guid, request); + } + + private QueryExpression create(Request request, ResourceDefinition resourceDefinition) throws InvalidQueryException { + String queryString; + if (request.getCardinality() == Request.Cardinality.INSTANCE) { + String idPropertyName = resourceDefinition.getIdPropertyName(); + queryString = String.format("%s:%s", idPropertyName, request.<String>getProperty(idPropertyName)); + } else { + queryString = request.getQueryString(); + } + + QueryExpression queryExpression; + if (queryString != null && !queryString.isEmpty()) { + QueryParser queryParser = new QueryParser(Version.LUCENE_48, "name", new KeywordAnalyzer()); + queryParser.setLowercaseExpandedTerms(false); + Query query; + try { + query = queryParser.parse((String) escape(queryString)); + } catch (ParseException e) { + throw new InvalidQueryException(e.getMessage()); + } + LOG.info("LuceneQuery: " + query); + queryExpression = create(query, resourceDefinition); + } else { + queryExpression = new AlwaysQueryExpression(); + } + // add query properties to request so that they are returned + request.addAdditionalSelectProperties(queryExpression.getProperties()); + return queryExpression; + } + + @SuppressWarnings("unchecked") + protected <T extends Query> QueryExpression create(T query, ResourceDefinition resourceDefinition) { + if (! expressionCreateFunctions.containsKey(query.getClass())) { + throw new CatalogRuntimeException("Query type currently not supported: " + query.getClass(), 400); + } + //todo: fix generic typing + ExpressionCreateFunction expressionCreateFunction = expressionCreateFunctions.get(query.getClass()); + return expressionCreateFunction.createExpression(query, resourceDefinition); + + } + + // "escapes" characters as necessary for lucene parser + //todo: currently '/' characters are blindly being replaced but this will not allow regex queries to be used + protected static Object escape(Object val) { + if (val instanceof String) { + return ((String)val).replaceAll("/", PATH_SEP_TOKEN); + } else { + return val; + } + } + + private abstract static class ExpressionCreateFunction<T extends Query> { + QueryExpression createExpression(T query, ResourceDefinition resourceDefinition) { + QueryExpression expression = create(query, resourceDefinition); + return expression.isProjectionExpression() ? + new ProjectionQueryExpression(expression, resourceDefinition) : + expression; + } + + protected abstract QueryExpression create(T query, ResourceDefinition resourceDefinition); + } + + private void registerExpressionCreateFunctions() { + expressionCreateFunctions.put(WildcardQuery.class, new ExpressionCreateFunction<WildcardQuery>() { + @Override + public QueryExpression create(WildcardQuery query, ResourceDefinition definition) { + return new WildcardQueryExpression(query, definition); + } + }); + + expressionCreateFunctions.put(PrefixQuery.class, new ExpressionCreateFunction<PrefixQuery>() { + @Override + public QueryExpression create(PrefixQuery query, ResourceDefinition definition) { + return new PrefixQueryExpression(query, definition); + } + }); + + expressionCreateFunctions.put(TermQuery.class, new ExpressionCreateFunction<TermQuery>() { + @Override + public QueryExpression create(TermQuery query, ResourceDefinition definition) { + return new TermQueryExpression(query, definition); + } + }); + + expressionCreateFunctions.put(TermRangeQuery.class, new ExpressionCreateFunction<TermRangeQuery>() { + @Override + public QueryExpression create(TermRangeQuery query, ResourceDefinition definition) { + return new TermRangeQueryExpression(query, definition); + } + }); + + expressionCreateFunctions.put(RegexQuery.class, new ExpressionCreateFunction<RegexQuery>() { + @Override + public QueryExpression create(RegexQuery query, ResourceDefinition definition) { + return new RegexQueryExpression(query, definition); + } + }); + + expressionCreateFunctions.put(BooleanQuery.class, new ExpressionCreateFunction<BooleanQuery>() { + @Override + public QueryExpression create(BooleanQuery query, ResourceDefinition definition) { + return new BooleanQueryExpression(query, definition, QueryFactory.this); + } + }); + } +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/catalog/src/main/java/org/apache/atlas/catalog/query/RegexQueryExpression.java ---------------------------------------------------------------------- diff --git a/catalog/src/main/java/org/apache/atlas/catalog/query/RegexQueryExpression.java b/catalog/src/main/java/org/apache/atlas/catalog/query/RegexQueryExpression.java new file mode 100644 index 0000000..c28d4d5 --- /dev/null +++ b/catalog/src/main/java/org/apache/atlas/catalog/query/RegexQueryExpression.java @@ -0,0 +1,41 @@ +/** + * 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.catalog.query; + +import org.apache.atlas.catalog.definition.ResourceDefinition; +import org.apache.lucene.sandbox.queries.regex.RegexQuery; + +import java.util.regex.Pattern; + +/** + * Query expression which evaluates a property against a regular expression. + */ +public class RegexQueryExpression extends BaseQueryExpression { + + public RegexQueryExpression(RegexQuery query, ResourceDefinition resourceDefinition) { + super(query.getField(), query.getTerm().text(), resourceDefinition); + + } + + @Override + public boolean evaluate(Object value) { + Pattern p = Pattern.compile(getExpectedValue()); + return value != null && p.matcher(String.valueOf(value)).matches(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/catalog/src/main/java/org/apache/atlas/catalog/query/TermQueryExpression.java ---------------------------------------------------------------------- diff --git a/catalog/src/main/java/org/apache/atlas/catalog/query/TermQueryExpression.java b/catalog/src/main/java/org/apache/atlas/catalog/query/TermQueryExpression.java new file mode 100644 index 0000000..a790866 --- /dev/null +++ b/catalog/src/main/java/org/apache/atlas/catalog/query/TermQueryExpression.java @@ -0,0 +1,52 @@ +/** + * 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.catalog.query; + +import org.apache.atlas.catalog.definition.ResourceDefinition; +import org.apache.lucene.search.TermQuery; + +import java.util.Collection; + +/** + * Query expression which evaluates whether a property equals a value. + */ +public class TermQueryExpression extends BaseQueryExpression { + + public TermQueryExpression(TermQuery query, ResourceDefinition resourceDefinition) { + super(query.getTerm().field(), query.getTerm().text(), resourceDefinition); + } + + @Override + public boolean evaluate(Object value) { + String expectedValue = getExpectedValue(); + if (value == null) { + return expectedValue.equals("null"); + //todo: refactor; we shouldn't need to use instanceof/cast here + } else if (value instanceof Collection) { + return ((Collection)value).contains(expectedValue); + } else { + return expectedValue.equals(QueryFactory.escape(String.valueOf(value))); + } + } + + public String getExpectedValue() { + return m_expectedValue; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/catalog/src/main/java/org/apache/atlas/catalog/query/TermRangeQueryExpression.java ---------------------------------------------------------------------- diff --git a/catalog/src/main/java/org/apache/atlas/catalog/query/TermRangeQueryExpression.java b/catalog/src/main/java/org/apache/atlas/catalog/query/TermRangeQueryExpression.java new file mode 100644 index 0000000..44cfb72 --- /dev/null +++ b/catalog/src/main/java/org/apache/atlas/catalog/query/TermRangeQueryExpression.java @@ -0,0 +1,61 @@ +/** + * 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.catalog.query; + +import org.apache.atlas.catalog.definition.ResourceDefinition; +import org.apache.lucene.search.TermRangeQuery; +import org.apache.lucene.util.BytesRef; + +/** + * Query expression which evaluates whether a property value is within a range. + */ +//todo: for month and year which are expressed via a single digit, must ensure that +//todo: a leading '0' is provided. For example, "2016-1-5" must be converted to "2016-01-05". +//todo: Month and day values aren't currently validated. +public class TermRangeQueryExpression extends BaseQueryExpression { + private final BytesRef m_lowerTerm; + private final BytesRef m_upperTerm; + private final boolean m_lowerInclusive; + private final boolean m_upperInclusive; + + public TermRangeQueryExpression(TermRangeQuery query, ResourceDefinition resourceDefinition) { + super(query.getField(), null, resourceDefinition); + m_lowerTerm = query.getLowerTerm(); + m_upperTerm = query.getUpperTerm(); + m_lowerInclusive = query.includesLower(); + m_upperInclusive = query.includesUpper(); + } + + @Override + public boolean evaluate(Object value) { + BytesRef valueBytes = new BytesRef(String.valueOf(value)); + return compareLowerBound(valueBytes) && compareUpperBound(valueBytes); + } + + private boolean compareLowerBound(BytesRef valueBytes) { + return m_lowerTerm == null || (m_lowerInclusive ? valueBytes.compareTo(m_lowerTerm) > 0 : + valueBytes.compareTo(m_lowerTerm) >= 0); + } + + private boolean compareUpperBound(BytesRef valueBytes) { + return m_upperTerm == null || (m_upperInclusive ? valueBytes.compareTo(m_upperTerm) < 0 : + valueBytes.compareTo(m_upperTerm) <= 0); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/catalog/src/main/java/org/apache/atlas/catalog/query/WildcardQueryExpression.java ---------------------------------------------------------------------- diff --git a/catalog/src/main/java/org/apache/atlas/catalog/query/WildcardQueryExpression.java b/catalog/src/main/java/org/apache/atlas/catalog/query/WildcardQueryExpression.java new file mode 100644 index 0000000..689891f --- /dev/null +++ b/catalog/src/main/java/org/apache/atlas/catalog/query/WildcardQueryExpression.java @@ -0,0 +1,43 @@ +/** + * 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.catalog.query; + +import org.apache.atlas.catalog.definition.ResourceDefinition; +import org.apache.lucene.search.WildcardQuery; + +import java.util.regex.Pattern; + +/** + * Query expression which evaluates values with wildcards. + * This differs from PrefixQueryExpression which handles expressions which end with a wildcard. + */ +public class WildcardQueryExpression extends BaseQueryExpression { + + public WildcardQueryExpression(WildcardQuery query, ResourceDefinition resourceDefinition) { + super(query.getTerm().field(), query.getTerm().text(), resourceDefinition); + } + + @Override + public boolean evaluate(Object value) { + // replace '*' with ".*" + // replace '?' with '.' + String regex = getExpectedValue().replaceAll("\\*", ".*").replaceAll("\\?", "."); + return Pattern.compile(regex).matcher(String.valueOf(value)).matches(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/catalog/src/test/java/org/apache/atlas/catalog/CollectionRequestTest.java ---------------------------------------------------------------------- diff --git a/catalog/src/test/java/org/apache/atlas/catalog/CollectionRequestTest.java b/catalog/src/test/java/org/apache/atlas/catalog/CollectionRequestTest.java new file mode 100644 index 0000000..0a2bace --- /dev/null +++ b/catalog/src/test/java/org/apache/atlas/catalog/CollectionRequestTest.java @@ -0,0 +1,74 @@ +/** + * 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.catalog; + +import org.testng.annotations.Test; + +import java.util.*; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + +/** + * Unit tests for CollectionRequest. + */ +public class CollectionRequestTest { + @Test + public void testNoProperties() { + String query = "name:foo*"; + Request request = new CollectionRequest(null, query); + + assertEquals(Request.Cardinality.COLLECTION, request.getCardinality()); + assertTrue(request.getProperties().isEmpty()); + assertNull(request.getProperty("foo")); + assertTrue(request.getAdditionalSelectProperties().isEmpty()); + } + + @Test + public void testWithProperties() { + String query = "name:foo*"; + Map<String, Object> properties = new HashMap<>(); + properties.put("foo", "fooValue"); + properties.put("someBoolean", true); + Request request = new CollectionRequest(properties, query); + + assertEquals(Request.Cardinality.COLLECTION, request.getCardinality()); + assertEquals(properties, request.getProperties()); + assertEquals("fooValue", request.getProperty("foo")); + assertTrue(request.<Boolean>getProperty("someBoolean")); + assertNull(request.getProperty("other")); + assertTrue(request.getAdditionalSelectProperties().isEmpty()); + } + + @Test + public void testSelectProperties() { + String query = "name:foo*"; + Request request = new CollectionRequest(null, query); + Collection<String> additionalSelectProps = new ArrayList<>(); + additionalSelectProps.add("foo"); + additionalSelectProps.add("bar"); + request.addAdditionalSelectProperties(additionalSelectProps); + Collection<String> requestAdditionalSelectProps = request.getAdditionalSelectProperties(); + assertEquals(2, requestAdditionalSelectProps.size()); + assertTrue(requestAdditionalSelectProps.contains("foo")); + assertTrue(requestAdditionalSelectProps.contains("bar")); + + } +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/catalog/src/test/java/org/apache/atlas/catalog/DefaultDateFormatterTest.java ---------------------------------------------------------------------- diff --git a/catalog/src/test/java/org/apache/atlas/catalog/DefaultDateFormatterTest.java b/catalog/src/test/java/org/apache/atlas/catalog/DefaultDateFormatterTest.java new file mode 100644 index 0000000..bbc98c5 --- /dev/null +++ b/catalog/src/test/java/org/apache/atlas/catalog/DefaultDateFormatterTest.java @@ -0,0 +1,41 @@ +/** + * 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.catalog; + +import org.testng.annotations.Test; + +import java.util.Calendar; +import java.util.GregorianCalendar; + +import static org.testng.Assert.assertEquals; + +/** + * Unit tests for DefaultDateFormatter. + */ +public class DefaultDateFormatterTest { + @Test + public void test() { + Calendar calendar = new GregorianCalendar(2016, 0, 20, 5, 10, 15); + long millis = calendar.getTimeInMillis(); + + DefaultDateFormatter dateFormatter = new DefaultDateFormatter(); + // month starts at 0 so we need to add 1 + assertEquals("2016-01-20:05:10:15", dateFormatter.format(millis)); + } +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/catalog/src/test/java/org/apache/atlas/catalog/DefaultPropertyMapperTest.java ---------------------------------------------------------------------- diff --git a/catalog/src/test/java/org/apache/atlas/catalog/DefaultPropertyMapperTest.java b/catalog/src/test/java/org/apache/atlas/catalog/DefaultPropertyMapperTest.java new file mode 100644 index 0000000..d37c041 --- /dev/null +++ b/catalog/src/test/java/org/apache/atlas/catalog/DefaultPropertyMapperTest.java @@ -0,0 +1,177 @@ +/** + * 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.catalog; + +import org.apache.atlas.typesystem.types.AttributeInfo; +import org.apache.atlas.typesystem.types.FieldMapping; +import org.apache.atlas.typesystem.types.HierarchicalType; +import org.testng.annotations.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.easymock.EasyMock.*; +import static org.testng.Assert.assertEquals; + +/** + * Unit tests for DefaultPropertyMapper. + */ +public class DefaultPropertyMapperTest { + @Test + public void testToCleanName_defaultMappings() { + String typeName = "testType"; + HierarchicalType dataType = createNiceMock(HierarchicalType.class); + + // currently only use key in map + Map<String, AttributeInfo> fields = new HashMap<>(); + fields.put("foo", null); + fields.put("prop", null); + // can't mock FieldMapping due to direct access to final instance var 'fields' + FieldMapping fieldMapping = new FieldMapping(fields, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + + // mock expectations + expect(dataType.fieldMapping()).andReturn(fieldMapping).anyTimes(); + replay(dataType); + + PropertyMapper propertyMapper = new TestDefaultPropertyMapper(dataType); + assertEquals(propertyMapper.toCleanName("Prefix.prop", typeName), "prop"); + assertEquals(propertyMapper.toCleanName("foo", typeName), "foo"); + assertEquals(propertyMapper.toCleanName("other", typeName), "other"); + assertEquals(propertyMapper.toCleanName("Prefix.other", typeName), "Prefix.other"); + + verify(dataType); + } + + @Test + public void testToQualifiedName_defaultMappings() throws Exception { + String typeName = "testType"; + HierarchicalType dataType = createNiceMock(HierarchicalType.class); + + // currently only use key in map + Map<String, AttributeInfo> fields = new HashMap<>(); + fields.put("foo", null); + fields.put("prop", null); + // can't mock FieldMapping due to direct access to final instance var 'fields' + FieldMapping fieldMapping = new FieldMapping(fields, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + + // mock expectations + expect(dataType.fieldMapping()).andReturn(fieldMapping).anyTimes(); + expect(dataType.getQualifiedName("foo")).andReturn("foo"); + expect(dataType.getQualifiedName("prop")).andReturn("Prefix.prop"); + replay(dataType); + + PropertyMapper propertyMapper = new TestDefaultPropertyMapper(dataType); + assertEquals(propertyMapper.toFullyQualifiedName("foo", typeName), "foo"); + assertEquals(propertyMapper.toFullyQualifiedName("prop", typeName), "Prefix.prop"); + assertEquals(propertyMapper.toFullyQualifiedName("other", typeName), "other"); + assertEquals(propertyMapper.toFullyQualifiedName("Prefix.other", typeName), "Prefix.other"); + + verify(dataType); + } + + @Test + public void testToCleanName_specifiedMappings() { + String typeName = "testType"; + HierarchicalType dataType = createNiceMock(HierarchicalType.class); + + // currently only use key in map + Map<String, AttributeInfo> fields = new HashMap<>(); + fields.put("foo", null); + fields.put("prop", null); + // can't mock FieldMapping due to direct access to final instance var 'fields' + FieldMapping fieldMapping = new FieldMapping(fields, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + + // mock expectations + expect(dataType.fieldMapping()).andReturn(fieldMapping).anyTimes(); + replay(dataType); + + Map<String, String> cleanToQualifiedMap = new HashMap<>(); + cleanToQualifiedMap.put("prop1", "property_1"); + Map<String, String> qualifiedToCleanMap = new HashMap<>(); + qualifiedToCleanMap.put("property_1", "prop1"); + + PropertyMapper propertyMapper = new TestDefaultPropertyMapper( + typeName, qualifiedToCleanMap, cleanToQualifiedMap, dataType); + + assertEquals(propertyMapper.toCleanName("property_1", typeName), "prop1"); + assertEquals(propertyMapper.toCleanName("Prefix.prop", typeName), "prop"); + assertEquals(propertyMapper.toCleanName("foo", typeName), "foo"); + assertEquals(propertyMapper.toCleanName("other", typeName), "other"); + assertEquals(propertyMapper.toCleanName("Prefix.other", typeName), "Prefix.other"); + + verify(dataType); + } + + @Test + public void testToQualifiedName_specifiedMappings() throws Exception { + String typeName = "testType"; + HierarchicalType dataType = createNiceMock(HierarchicalType.class); + + // currently only use key in map + Map<String, AttributeInfo> fields = new HashMap<>(); + fields.put("foo", null); + fields.put("prop", null); + // can't mock FieldMapping due to direct access to final instance var 'fields' + FieldMapping fieldMapping = new FieldMapping(fields, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + + // mock expectations + expect(dataType.fieldMapping()).andReturn(fieldMapping).anyTimes(); + expect(dataType.getQualifiedName("foo")).andReturn("foo"); + expect(dataType.getQualifiedName("prop")).andReturn("Prefix.prop"); + replay(dataType); + + Map<String, String> cleanToQualifiedMap = new HashMap<>(); + cleanToQualifiedMap.put("prop1", "property_1"); + Map<String, String> qualifiedToCleanMap = new HashMap<>(); + qualifiedToCleanMap.put("property_1", "prop1"); + + PropertyMapper propertyMapper = new TestDefaultPropertyMapper( + typeName, qualifiedToCleanMap, cleanToQualifiedMap, dataType); + + assertEquals(propertyMapper.toFullyQualifiedName("prop1", typeName), "property_1"); + assertEquals(propertyMapper.toFullyQualifiedName("foo", typeName), "foo"); + assertEquals(propertyMapper.toFullyQualifiedName("prop", typeName), "Prefix.prop"); + assertEquals(propertyMapper.toFullyQualifiedName("other", typeName), "other"); + assertEquals(propertyMapper.toFullyQualifiedName("Prefix.other", typeName), "Prefix.other"); + + verify(dataType); + } + + private static class TestDefaultPropertyMapper extends DefaultPropertyMapper { + private HierarchicalType dataType; + public TestDefaultPropertyMapper(HierarchicalType dataType) { + super(); + this.dataType = dataType; + } + + public TestDefaultPropertyMapper(String type, + Map<String, String> qualifiedToCleanMap, + Map<String, String> cleanToQualifiedMap, + HierarchicalType dataType) { + + super(qualifiedToCleanMap, cleanToQualifiedMap); + this.dataType = dataType; + } + + @Override + protected HierarchicalType createDataType(String type) { + return dataType; + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/catalog/src/test/java/org/apache/atlas/catalog/EntityResourceProviderTest.java ---------------------------------------------------------------------- diff --git a/catalog/src/test/java/org/apache/atlas/catalog/EntityResourceProviderTest.java b/catalog/src/test/java/org/apache/atlas/catalog/EntityResourceProviderTest.java new file mode 100644 index 0000000..2f29103 --- /dev/null +++ b/catalog/src/test/java/org/apache/atlas/catalog/EntityResourceProviderTest.java @@ -0,0 +1,215 @@ +/** + * 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.catalog; + +import org.apache.atlas.catalog.exception.ResourceNotFoundException; +import org.apache.atlas.catalog.query.AtlasQuery; +import org.apache.atlas.catalog.query.QueryFactory; +import org.easymock.Capture; +import org.testng.annotations.Test; + +import java.util.*; + +import static org.easymock.EasyMock.*; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + +/** + * Unit Tests for EntityResourceProvider. + */ +public class EntityResourceProviderTest { + @Test + public void testGetResource() throws Exception { + AtlasTypeSystem typeSystem = createStrictMock(AtlasTypeSystem.class); + QueryFactory queryFactory = createStrictMock(QueryFactory.class); + AtlasQuery query = createStrictMock(AtlasQuery.class); + Capture<Request> requestCapture = newCapture(); + + Collection<Map<String, Object>> queryResult = new ArrayList<>(); + Map<String, Object> queryResultRow = new HashMap<>(); + queryResult.add(queryResultRow); + queryResultRow.put("id", "1"); + queryResultRow.put("creation_time", "04/20/2016"); + + // mock expectations + expect(queryFactory.createEntityQuery(capture(requestCapture))).andReturn(query); + expect(query.execute()).andReturn(queryResult); + replay(typeSystem, queryFactory, query); + + EntityResourceProvider provider = new EntityResourceProvider(typeSystem); + provider.setQueryFactory(queryFactory); + + Map<String, Object> requestProperties = new HashMap<>(); + requestProperties.put("id", "1"); + Request userRequest = new InstanceRequest(requestProperties); + + Result result = provider.getResourceById(userRequest); + + assertEquals(1, result.getPropertyMaps().size()); + assertEquals(queryResultRow, result.getPropertyMaps().iterator().next()); + + Request request = requestCapture.getValue(); + assertNull(request.getQueryString()); + assertEquals(0, request.getAdditionalSelectProperties().size()); + assertEquals(requestProperties, request.getProperties()); + + verify(typeSystem, queryFactory, query); + } + + @Test(expectedExceptions = ResourceNotFoundException.class) + public void testGetResource_404() throws Exception { + AtlasTypeSystem typeSystem = createStrictMock(AtlasTypeSystem.class); + QueryFactory queryFactory = createStrictMock(QueryFactory.class); + AtlasQuery query = createStrictMock(AtlasQuery.class); + Capture<Request> requestCapture = newCapture(); + + // empty response should result in a ResourceNotFoundException + Collection<Map<String, Object>> emptyResponse = new ArrayList<>(); + + // mock expectations + expect(queryFactory.createEntityQuery(capture(requestCapture))).andReturn(query); + expect(query.execute()).andReturn(emptyResponse); + replay(typeSystem, queryFactory, query); + + EntityResourceProvider provider = new EntityResourceProvider(typeSystem); + provider.setQueryFactory(queryFactory); + + Map<String, Object> requestProperties = new HashMap<>(); + requestProperties.put("id", "1"); + Request request = new InstanceRequest(requestProperties); + + provider.getResourceById(request); + + verify(typeSystem, queryFactory, query); + } + + @Test + public void testGetResources() throws Exception { + AtlasTypeSystem typeSystem = createStrictMock(AtlasTypeSystem.class); + QueryFactory queryFactory = createStrictMock(QueryFactory.class); + AtlasQuery query = createStrictMock(AtlasQuery.class); + Capture<Request> requestCapture = newCapture(); + + Collection<Map<String, Object>> queryResult = new ArrayList<>(); + Map<String, Object> queryResultRow1 = new HashMap<>(); + queryResult.add(queryResultRow1); + queryResultRow1.put("mame", "entity1"); + queryResultRow1.put("description", "test entity description"); + queryResultRow1.put("creation_time", "04/20/2016"); + + Map<String, Object> queryResultRow2 = new HashMap<>(); + queryResult.add(queryResultRow2); + queryResultRow2.put("mame", "entity2"); + queryResultRow2.put("description", "test entity description 2"); + queryResultRow2.put("creation_time", "04/21/2016"); + + // mock expectations + expect(queryFactory.createEntityQuery(capture(requestCapture))).andReturn(query); + expect(query.execute()).andReturn(queryResult); + replay(typeSystem, queryFactory, query); + + EntityResourceProvider provider = new EntityResourceProvider(typeSystem); + provider.setQueryFactory(queryFactory); + + Request userRequest = new CollectionRequest(Collections.<String, Object>emptyMap(), "name:entity*"); + Result result = provider.getResources(userRequest); + + assertEquals(2, result.getPropertyMaps().size()); + assertTrue(result.getPropertyMaps().contains(queryResultRow1)); + assertTrue(result.getPropertyMaps().contains(queryResultRow2)); + + Request request = requestCapture.getValue(); + assertEquals("name:entity*", request.getQueryString()); + assertEquals(0, request.getAdditionalSelectProperties().size()); + assertEquals(0, request.getProperties().size()); + + verify(typeSystem, queryFactory, query); + } + + @Test + public void testGetResources_noResults() throws Exception { + AtlasTypeSystem typeSystem = createStrictMock(AtlasTypeSystem.class); + QueryFactory queryFactory = createStrictMock(QueryFactory.class); + AtlasQuery query = createStrictMock(AtlasQuery.class); + Capture<Request> requestCapture = newCapture(); + + // empty result shouldn't result in exception for collection query + Collection<Map<String, Object>> queryResult = new ArrayList<>(); + + // mock expectations + expect(queryFactory.createEntityQuery(capture(requestCapture))).andReturn(query); + expect(query.execute()).andReturn(queryResult); + replay(typeSystem, queryFactory, query); + + EntityResourceProvider provider = new EntityResourceProvider(typeSystem); + provider.setQueryFactory(queryFactory); + + Request userRequest = new CollectionRequest(Collections.<String, Object>emptyMap(), "name:entity*"); + Result result = provider.getResources(userRequest); + + assertEquals(0, result.getPropertyMaps().size()); + + Request request = requestCapture.getValue(); + assertEquals("name:entity*", request.getQueryString()); + assertEquals(0, request.getAdditionalSelectProperties().size()); + assertEquals(0, request.getProperties().size()); + + verify(typeSystem, queryFactory, query); + } + + @Test(expectedExceptions = UnsupportedOperationException.class) + public void testCreateResource() throws Exception { + AtlasTypeSystem typeSystem = createStrictMock(AtlasTypeSystem.class); + QueryFactory queryFactory = createStrictMock(QueryFactory.class); + AtlasQuery query = createStrictMock(AtlasQuery.class); + + // mock expectations + replay(typeSystem, queryFactory, query); + + Map<String, Object> requestProperties = new HashMap<>(); + requestProperties.put("id", "1"); + Request userRequest = new InstanceRequest(requestProperties); + + EntityResourceProvider provider = new EntityResourceProvider(typeSystem); + provider.setQueryFactory(queryFactory); + + provider.createResource(userRequest); + } + + @Test(expectedExceptions = UnsupportedOperationException.class) + public void testCreateResources() throws Exception { + AtlasTypeSystem typeSystem = createStrictMock(AtlasTypeSystem.class); + QueryFactory queryFactory = createStrictMock(QueryFactory.class); + AtlasQuery query = createStrictMock(AtlasQuery.class); + + // mock expectations + replay(typeSystem, queryFactory, query); + + Map<String, Object> requestProperties = new HashMap<>(); + requestProperties.put("id", "1"); + Request userRequest = new InstanceRequest(requestProperties); + + EntityResourceProvider provider = new EntityResourceProvider(typeSystem); + provider.setQueryFactory(queryFactory); + + provider.createResources(userRequest); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/aaf2971a/catalog/src/test/java/org/apache/atlas/catalog/EntityTagResourceProviderTest.java ---------------------------------------------------------------------- diff --git a/catalog/src/test/java/org/apache/atlas/catalog/EntityTagResourceProviderTest.java b/catalog/src/test/java/org/apache/atlas/catalog/EntityTagResourceProviderTest.java new file mode 100644 index 0000000..a95b966 --- /dev/null +++ b/catalog/src/test/java/org/apache/atlas/catalog/EntityTagResourceProviderTest.java @@ -0,0 +1,444 @@ +/** + * 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.catalog; + +import org.apache.atlas.catalog.exception.CatalogException; +import org.apache.atlas.catalog.exception.InvalidPayloadException; +import org.apache.atlas.catalog.exception.ResourceAlreadyExistsException; +import org.apache.atlas.catalog.exception.ResourceNotFoundException; +import org.apache.atlas.catalog.query.AtlasQuery; +import org.apache.atlas.catalog.query.QueryFactory; +import org.easymock.Capture; +import org.testng.annotations.Test; + +import java.util.*; + +import static org.easymock.EasyMock.*; +import static org.easymock.EasyMock.createStrictMock; +import static org.easymock.EasyMock.replay; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + +/** + * Unit tests for EntityTagResourceProvider. + */ +public class EntityTagResourceProviderTest { + @Test + public void testGetResource() throws Exception { + AtlasTypeSystem typeSystem = createStrictMock(AtlasTypeSystem.class); + QueryFactory queryFactory = createStrictMock(QueryFactory.class); + AtlasQuery query = createStrictMock(AtlasQuery.class); + Capture<Request> requestCapture = newCapture(); + + Collection<Map<String, Object>> queryResult = new ArrayList<>(); + Map<String, Object> queryResultRow = new HashMap<>(); + queryResult.add(queryResultRow); + queryResultRow.put("name", "taxonomyName.termName"); + queryResultRow.put("description", "test term description"); + + // mock expectations + expect(queryFactory.createEntityTagQuery(capture(requestCapture))).andReturn(query); + expect(query.execute()).andReturn(queryResult); + replay(typeSystem, queryFactory, query); + + EntityTagResourceProvider provider = new EntityTagResourceProvider(typeSystem); + provider.setQueryFactory(queryFactory); + + Map<String, Object> requestProperties = new HashMap<>(); + requestProperties.put("name", "taxonomyName.termName"); + requestProperties.put("id", "1"); + Request userRequest = new InstanceRequest(requestProperties); + + Result result = provider.getResourceById(userRequest); + + assertEquals(1, result.getPropertyMaps().size()); + assertEquals(queryResultRow, result.getPropertyMaps().iterator().next()); + + Request request = requestCapture.getValue(); + assertNull(request.getQueryString()); + assertEquals(0, request.getAdditionalSelectProperties().size()); + assertEquals(2, request.getProperties().size()); + assertEquals("taxonomyName.termName", request.getProperties().get("name")); + assertEquals(Request.Cardinality.INSTANCE, request.getCardinality()); + + verify(typeSystem, queryFactory, query); + } + + @Test(expectedExceptions = ResourceNotFoundException.class) + public void testGetResource_404() throws Exception { + AtlasTypeSystem typeSystem = createStrictMock(AtlasTypeSystem.class); + QueryFactory queryFactory = createStrictMock(QueryFactory.class); + AtlasQuery query = createStrictMock(AtlasQuery.class); + Capture<Request> requestCapture = newCapture(); + + // empty response should result in a ResourceNotFoundException + Collection<Map<String, Object>> emptyResponse = new ArrayList<>(); + + // mock expectations + expect(queryFactory.createEntityTagQuery(capture(requestCapture))).andReturn(query); + expect(query.execute()).andReturn(emptyResponse); + replay(typeSystem, queryFactory, query); + + EntityTagResourceProvider provider = new EntityTagResourceProvider(typeSystem); + provider.setQueryFactory(queryFactory); + + Map<String, Object> requestProperties = new HashMap<>(); + requestProperties.put("name", "taxonomyName.termName"); + requestProperties.put("id", "1"); + Request request = new InstanceRequest(requestProperties); + + provider.getResourceById(request); + } + + @Test + public void testGetResources() throws Exception { + AtlasTypeSystem typeSystem = createStrictMock(AtlasTypeSystem.class); + QueryFactory queryFactory = createStrictMock(QueryFactory.class); + AtlasQuery query = createStrictMock(AtlasQuery.class); + Capture<Request> requestCapture = newCapture(); + + Collection<Map<String, Object>> queryResult = new ArrayList<>(); + Map<String, Object> queryResultRow1 = new HashMap<>(); + queryResult.add(queryResultRow1); + queryResultRow1.put("name", "testTaxonomy.termName"); + queryResultRow1.put("description", "test term description"); + + Map<String, Object> queryResultRow2 = new HashMap<>(); + queryResult.add(queryResultRow2); + queryResultRow2.put("name", "testTaxonomy.termName2"); + queryResultRow2.put("description", "test term 2 description"); + // mock expectations + expect(queryFactory.createEntityTagQuery(capture(requestCapture))).andReturn(query); + expect(query.execute()).andReturn(queryResult); + replay(typeSystem, queryFactory, query); + + EntityTagResourceProvider provider = new EntityTagResourceProvider(typeSystem); + provider.setQueryFactory(queryFactory); + + Map<String, Object> requestProperties = new HashMap<>(); + requestProperties.put("id", "1"); + Request userRequest = new CollectionRequest(requestProperties, "name:testTaxonomy.*"); + // invoke test method + Result result = provider.getResources(userRequest); + + assertEquals(2, result.getPropertyMaps().size()); + assertTrue(result.getPropertyMaps().contains(queryResultRow1)); + assertTrue(result.getPropertyMaps().contains(queryResultRow2)); + + Request request = requestCapture.getValue(); + assertEquals("name:testTaxonomy.*", request.getQueryString()); + assertEquals(0, request.getAdditionalSelectProperties().size()); + + verify(typeSystem, queryFactory, query); + } + + @Test + public void testGetResources_noResults() throws Exception { + AtlasTypeSystem typeSystem = createStrictMock(AtlasTypeSystem.class); + QueryFactory queryFactory = createStrictMock(QueryFactory.class); + AtlasQuery query = createStrictMock(AtlasQuery.class); + Capture<Request> requestCapture = newCapture(); + + Collection<Map<String, Object>> queryResult = new ArrayList<>(); + + // mock expectations + expect(queryFactory.createEntityTagQuery(capture(requestCapture))).andReturn(query); + expect(query.execute()).andReturn(queryResult); + replay(typeSystem, queryFactory, query); + + EntityTagResourceProvider provider = new EntityTagResourceProvider(typeSystem); + provider.setQueryFactory(queryFactory); + + Map<String, Object> requestProperties = new HashMap<>(); + requestProperties.put("id", "1"); + Request userRequest = new CollectionRequest(requestProperties, "name:testTaxonomy.*"); + // invoke test method + Result result = provider.getResources(userRequest); + + assertEquals(0, result.getPropertyMaps().size()); + + Request request = requestCapture.getValue(); + assertEquals("name:testTaxonomy.*", request.getQueryString()); + assertEquals(0, request.getAdditionalSelectProperties().size()); + + verify(typeSystem, queryFactory, query); + } + + @Test(expectedExceptions = InvalidPayloadException.class) + public void testCreateResource_invalidRequest__noName() throws Exception { + AtlasTypeSystem typeSystem = createStrictMock(AtlasTypeSystem.class); + QueryFactory queryFactory = createStrictMock(QueryFactory.class); + AtlasQuery query = createStrictMock(AtlasQuery.class); + + replay(typeSystem, queryFactory, query); + + Map<String, Object> requestProperties = new HashMap<>(); + // missing name name should result in InvalidPayloadException + requestProperties.put("description", "description"); + Request userRequest = new InstanceRequest(requestProperties); + + EntityTagResourceProvider provider = new EntityTagResourceProvider(typeSystem); + provider.setQueryFactory(queryFactory); + + provider.createResource(userRequest); + } + + @Test + public void testCreateResource() throws Exception { + AtlasTypeSystem typeSystem = createStrictMock(AtlasTypeSystem.class); + QueryFactory queryFactory = createStrictMock(QueryFactory.class); + AtlasQuery query = createStrictMock(AtlasQuery.class); + ResourceProvider termResourceProvider = createStrictMock(TermResourceProvider.class); + Capture<Request> termRequestCapture = newCapture(); + + Collection<Map<String, Object>> termQueryResult = new ArrayList<>(); + Map<String, Object> termQueryResultRow = new HashMap<>(); + termQueryResult.add(termQueryResultRow); + termQueryResultRow.put("name", "testTaxonomy.termName"); + termQueryResultRow.put("type", "testTaxonomy.termName"); + termQueryResultRow.put("available_as_tag", true); + termQueryResultRow.put("description", "term description"); + Result termResult = new Result(termQueryResult); + + // mock expectations + expect(termResourceProvider.getResourceById(capture(termRequestCapture))).andReturn(termResult); + Map<String, Object> tagProperties = new HashMap<>(); + tagProperties.put("name", "testTaxonomy.termName"); + tagProperties.put("description", "term description"); + typeSystem.createTraitInstance("11-22-33", "testTaxonomy.termName", tagProperties); + replay(typeSystem, queryFactory, query, termResourceProvider); + + EntityTagResourceProvider provider = new TestEntityTagResourceProvider(typeSystem, termResourceProvider); + provider.setQueryFactory(queryFactory); + + Map<String, Object> requestProperties = new HashMap<>(); + requestProperties.put("name", "testTaxonomy.termName"); + requestProperties.put("id", "11-22-33"); + Request userRequest = new InstanceRequest(requestProperties); + + provider.createResource(userRequest); + + Request termRequest = termRequestCapture.getValue(); + Map<String, Object> termRequestProps = termRequest.getProperties(); + assertEquals(1, termRequestProps.size()); + TermPath termPath = (TermPath) termRequestProps.get("termPath"); + assertEquals("testTaxonomy.termName", termPath.getFullyQualifiedName()); + assertEquals(1, termRequest.getAdditionalSelectProperties().size()); + assertEquals("type", termRequest.getAdditionalSelectProperties().iterator().next()); + assertNull(termRequest.getQueryString()); + + verify(typeSystem, queryFactory, query, termResourceProvider); + } + + @Test(expectedExceptions = CatalogException.class) + public void testCreateResource_invalidRequest__termNotAvailableForTagging() throws Exception { + AtlasTypeSystem typeSystem = createStrictMock(AtlasTypeSystem.class); + QueryFactory queryFactory = createStrictMock(QueryFactory.class); + AtlasQuery query = createStrictMock(AtlasQuery.class); + ResourceProvider termResourceProvider = createStrictMock(TermResourceProvider.class); + Capture<Request> termRequestCapture = newCapture(); + + Collection<Map<String, Object>> termQueryResult = new ArrayList<>(); + Map<String, Object> termQueryResultRow = new HashMap<>(); + termQueryResult.add(termQueryResultRow); + termQueryResultRow.put("name", "testTaxonomy.termName"); + termQueryResultRow.put("type", "testTaxonomy.termName"); + // false value for 'available_as_tag' should result in an exception + termQueryResultRow.put("available_as_tag", false); + termQueryResultRow.put("description", "term description"); + Result termResult = new Result(termQueryResult); + + // mock expectations + expect(termResourceProvider.getResourceById(capture(termRequestCapture))).andReturn(termResult); + replay(typeSystem, queryFactory, query, termResourceProvider); + + EntityTagResourceProvider provider = new TestEntityTagResourceProvider(typeSystem, termResourceProvider); + provider.setQueryFactory(queryFactory); + + Map<String, Object> requestProperties = new HashMap<>(); + requestProperties.put("name", "testTaxonomy.termName"); + requestProperties.put("id", "11-22-33"); + Request userRequest = new InstanceRequest(requestProperties); + + provider.createResource(userRequest); + } + + @Test(expectedExceptions = ResourceAlreadyExistsException.class) + public void testCreateResource_invalidRequest__alreadyExists() throws Exception { + AtlasTypeSystem typeSystem = createStrictMock(AtlasTypeSystem.class); + QueryFactory queryFactory = createStrictMock(QueryFactory.class); + AtlasQuery query = createStrictMock(AtlasQuery.class); + ResourceProvider termResourceProvider = createStrictMock(TermResourceProvider.class); + Capture<Request> termRequestCapture = newCapture(); + + Collection<Map<String, Object>> termQueryResult = new ArrayList<>(); + Map<String, Object> termQueryResultRow = new HashMap<>(); + termQueryResult.add(termQueryResultRow); + termQueryResultRow.put("name", "testTaxonomy.termName"); + termQueryResultRow.put("type", "testTaxonomy.termName"); + termQueryResultRow.put("available_as_tag", true); + termQueryResultRow.put("description", "term description"); + Result termResult = new Result(termQueryResult); + + // mock expectations + expect(termResourceProvider.getResourceById(capture(termRequestCapture))).andReturn(termResult); + Map<String, Object> tagProperties = new HashMap<>(); + tagProperties.put("name", "testTaxonomy.termName"); + tagProperties.put("description", "term description"); + typeSystem.createTraitInstance("11-22-33", "testTaxonomy.termName", tagProperties); + expectLastCall().andThrow(new ResourceAlreadyExistsException("")); + replay(typeSystem, queryFactory, query, termResourceProvider); + + EntityTagResourceProvider provider = new TestEntityTagResourceProvider(typeSystem, termResourceProvider); + provider.setQueryFactory(queryFactory); + + Map<String, Object> requestProperties = new HashMap<>(); + requestProperties.put("name", "testTaxonomy.termName"); + requestProperties.put("id", "11-22-33"); + Request userRequest = new InstanceRequest(requestProperties); + + provider.createResource(userRequest); + } + + @Test + public void testCreateResources() throws Exception { + AtlasTypeSystem typeSystem = createMock(AtlasTypeSystem.class); + QueryFactory queryFactory = createStrictMock(QueryFactory.class); + AtlasQuery entityQuery = createMock(AtlasQuery.class); + ResourceProvider termResourceProvider = createMock(TermResourceProvider.class); + Capture<Request> entityRequestCapture = newCapture(); + Capture<Request> termRequestCapture1 = newCapture(); + Capture<Request> termRequestCapture2 = newCapture(); + + Collection<Map<String, Object>> entityQueryResult = new ArrayList<>(); + Map<String, Object> entityQueryResultRow = new HashMap<>(); + entityQueryResultRow.put("id", "1"); + entityQueryResult.add(entityQueryResultRow); + + Map<String, Object> entityQueryResultRow2 = new HashMap<>(); + entityQueryResultRow2.put("id", "2"); + entityQueryResult.add(entityQueryResultRow2); + + Collection<Map<String, Object>> termQueryResult1 = new ArrayList<>(); + Map<String, Object> termQueryResultRow1 = new HashMap<>(); + termQueryResult1.add(termQueryResultRow1); + termQueryResultRow1.put("name", "testTaxonomy.termName1"); + termQueryResultRow1.put("type", "testTaxonomy.termName1"); + termQueryResultRow1.put("available_as_tag", true); + termQueryResultRow1.put("description", "term description"); + Result termResult1 = new Result(termQueryResult1); + + Collection<Map<String, Object>> termQueryResult2 = new ArrayList<>(); + Map<String, Object> termQueryResultRow2 = new HashMap<>(); + termQueryResult2.add(termQueryResultRow2); + termQueryResultRow2.put("name", "testTaxonomy.termName2"); + termQueryResultRow2.put("type", "testTaxonomy.termName2"); + termQueryResultRow2.put("available_as_tag", true); + termQueryResultRow2.put("description", "term 2 description"); + Result termResult2 = new Result(termQueryResult2); + + // mock expectations + expect(queryFactory.createEntityQuery(capture(entityRequestCapture))).andReturn(entityQuery); + expect(entityQuery.execute()).andReturn(entityQueryResult); + + expect(termResourceProvider.getResourceById(capture(termRequestCapture1))).andReturn(termResult1); + expect(termResourceProvider.getResourceById(capture(termRequestCapture2))).andReturn(termResult2); + + Map<String, Object> tagProperties1 = new HashMap<>(); + tagProperties1.put("name", "testTaxonomy.termName1"); + tagProperties1.put("description", "term description"); + // each tag is associated with each entity + typeSystem.createTraitInstance("1", "testTaxonomy.termName1", tagProperties1); + typeSystem.createTraitInstance("2", "testTaxonomy.termName1", tagProperties1); + + Map<String, Object> tagProperties2 = new HashMap<>(); + tagProperties2.put("name", "testTaxonomy.termName2"); + tagProperties2.put("description", "term 2 description"); + // each tag is associated with each entity + typeSystem.createTraitInstance("1", "testTaxonomy.termName2", tagProperties2); + typeSystem.createTraitInstance("2", "testTaxonomy.termName2", tagProperties2); + + replay(typeSystem, queryFactory, entityQuery, termResourceProvider); + // end mock expectations + + EntityTagResourceProvider provider = new TestEntityTagResourceProvider(typeSystem, termResourceProvider); + provider.setQueryFactory(queryFactory); + + Map<String, Object> requestProps = new HashMap<>(); + Collection<Map<String, String>> tagMaps = new ArrayList<>(); + requestProps.put("tags", tagMaps); + Map<String, String> tagMap1 = new HashMap<>(); + tagMap1.put("name", "testTaxonomy.termName1"); + tagMaps.add(tagMap1); + Map<String, String> tagMap2 = new HashMap<>(); + tagMap2.put("name", "testTaxonomy.termName2"); + tagMaps.add(tagMap2); + + Request userRequest = new CollectionRequest(requestProps, "name:foo*"); + // invoke method being tested + Collection<String> createResult = provider.createResources(userRequest); + + assertEquals(4, createResult.size()); + assertTrue(createResult.contains("v1/entities/1/tags/testTaxonomy.termName1")); + assertTrue(createResult.contains("v1/entities/1/tags/testTaxonomy.termName2")); + assertTrue(createResult.contains("v1/entities/2/tags/testTaxonomy.termName1")); + assertTrue(createResult.contains("v1/entities/2/tags/testTaxonomy.termName2")); + + Request entityRequest = entityRequestCapture.getValue(); + assertEquals("name:foo*", entityRequest.getQueryString()); + assertEquals(Request.Cardinality.COLLECTION, entityRequest.getCardinality()); + + Request termRequest1 = termRequestCapture1.getValue(); + assertNull(termRequest1.getQueryString()); + assertEquals(Request.Cardinality.INSTANCE, termRequest1.getCardinality()); + Map<String, Object> termRequestProps = termRequest1.getProperties(); + assertEquals(1, termRequestProps.size()); + TermPath termPath = (TermPath) termRequestProps.get("termPath"); + assertEquals("testTaxonomy.termName1", termPath.getFullyQualifiedName()); + + Request termRequest2 = termRequestCapture2.getValue(); + assertNull(termRequest2.getQueryString()); + assertEquals(Request.Cardinality.INSTANCE, termRequest2.getCardinality()); + Map<String, Object> termRequestProps2 = termRequest2.getProperties(); + assertEquals(1, termRequestProps2.size()); + TermPath termPath2 = (TermPath) termRequestProps2.get("termPath"); + assertEquals("testTaxonomy.termName2", termPath2.getFullyQualifiedName()); + + verify(typeSystem, queryFactory, entityQuery, termResourceProvider); + } + + //todo: test behavior of createResources in case of partial success after behavior is defined + + + private static class TestEntityTagResourceProvider extends EntityTagResourceProvider { + + private ResourceProvider testTermResourceProvider; + + public TestEntityTagResourceProvider(AtlasTypeSystem typeSystem, ResourceProvider termResourceProvider) { + super(typeSystem); + testTermResourceProvider = termResourceProvider; + } + + @Override + protected synchronized ResourceProvider getTermResourceProvider() { + return testTermResourceProvider; + } + } +}
