You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@atlas.apache.org by yh...@apache.org on 2016/05/18 13:03:59 UTC

[3/5] incubator-atlas git commit: ATLAS-491 Business Catalog / Taxonomy (jspeidel via yhemanth)

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;
+        }
+    }
+}