/* * Copyright 2015-2016 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.hawkular.inventory.impl.tinkerpop; import static org.hawkular.inventory.api.Relationships.WellKnown.contains; import static org.hawkular.inventory.api.Relationships.WellKnown.hasData; import static org.hawkular.inventory.impl.tinkerpop.HawkularTraversal.hwk__; import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.InternalEdge.__inState; import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.InternalEdge.__withIdentityHash; import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Property.__cp; import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Property.__eid; import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Property.__sourceCp; import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Property.__sourceEid; import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Property.__sourceType; import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Property.__targetCp; import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Property.__targetEid; import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Property.__targetType; import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Property.__type; import java.util.Arrays; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Stream; import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; import org.apache.tinkerpop.gremlin.structure.Direction; import org.apache.tinkerpop.gremlin.structure.T; import org.hawkular.inventory.api.filters.Filter; import org.hawkular.inventory.api.filters.Marker; import org.hawkular.inventory.api.filters.RecurseFilter; import org.hawkular.inventory.api.filters.Related; import org.hawkular.inventory.api.filters.RelationWith; import org.hawkular.inventory.api.filters.SwitchElementType; import org.hawkular.inventory.api.filters.With; import org.hawkular.inventory.api.model.Entity; import org.hawkular.inventory.api.model.StructuredData; import org.hawkular.inventory.base.spi.Discriminator; import org.hawkular.inventory.base.spi.NoopFilter; import org.hawkular.inventory.impl.tinkerpop.spi.Constants; import org.hawkular.inventory.paths.Path; import org.hawkular.inventory.paths.RelativePath; import org.hawkular.inventory.paths.SegmentType; /** * @author Lukas Krejci * @author Jirka Kremser * @since 0.0.1 */ class FilterVisitor { private static final AtomicLong CNT = new AtomicLong(); public void visit(Discriminator discriminator, HawkularTraversal<?, ?> query, Related related, QueryTranslationState state) { if (state.isInEdges()) { //jump to vertices switch (state.getComingFrom()) { case OUT: query.inV(); break; case IN: query.outV(); break; case BOTH: query.bothV(); } } boolean applied = false; switch (related.getEntityRole()) { case TARGET: if (null != related.getRelationshipName()) { state.setInEdges(true); state.setComingFrom(Direction.IN); query.inE(related.getRelationshipName()).restrictTo(discriminator); applied = true; } if (null != related.getRelationshipId()) { // TODO test if (applied) { query.has(__eid.name(), related.getRelationshipId()); } else { query.inE().has(__eid.name(), related.getRelationshipId()); } query.restrictTo(discriminator); } break; case SOURCE: if (null != related.getRelationshipName()) { state.setInEdges(true); state.setComingFrom(Direction.OUT); query.outE(related.getRelationshipName()).restrictTo(discriminator); applied = true; } if (null != related.getRelationshipId()) { // TODO test if (applied) { query.has(__eid.name(), related.getRelationshipId()); } else { query.outE().has(__eid.name(), related.getRelationshipId()); } query.restrictTo(discriminator); } break; case ANY: // TODO properties-on-edges optimization not implemented for direction "both" if (null != related.getRelationshipName()) { query.bothE(related.getRelationshipName()).restrictTo(discriminator).bothV(); } if (null != related.getRelationshipId()) { // TODO test query.bothE().restrictTo(discriminator).has(__eid.name(), related.getRelationshipId()).bothV(); } } if (related.getEntityPath() != null) { String prop = chooseBasedOnDirection(Constants.Property.__cp, Constants.Property.__targetCp, Constants .Property.__sourceCp, TinkerpopBackend.asDirection(related.getEntityRole())).name(); query.has(prop, related.getEntityPath().toString()); } } @SuppressWarnings("unchecked") public void visit(Discriminator discriminator, HawkularTraversal<?, ?> query, With.Ids ids, QueryTranslationState state) { String prop = propertyNameBasedOnState(__eid, state); if (ids.getIds().length == 1) { query.has(prop, ids.getIds()[0]); } else { query.has(prop, P.within(ids.getIds())); } goBackFromEdges(query, state); query.existsAt(discriminator); } @SuppressWarnings("unchecked") public void visit(Discriminator discriminator, HawkularTraversal<?, ?> query, With.Types types, QueryTranslationState state) { String prop = propertyNameBasedOnState(__type, state); if (types.getTypes().length == 1) { Constants.Type type = Constants.Type.of(types.getTypes()[0]); query.has(prop, type.name()); goBackFromEdges(query, state); query.existsAt(discriminator); return; } String[] typeNames = Stream.of(types.getTypes()).map(t -> Constants.Type.of(t).name()) .toArray(String[]::new); query.has(prop, P.within(typeNames)); goBackFromEdges(query, state); query.existsAt(discriminator); } @SuppressWarnings("unchecked") public void visit(Discriminator discriminator, HawkularTraversal<?, ?> query, With.Names names, QueryTranslationState state) { goBackFromEdges(query, state); query.existsAt(discriminator); String prop = Constants.Property.name.name(); HawkularTraversal<?, ?> nameCheck = hwk__().outE(__inState.name()).restrictTo(discriminator).inV(); if (names.getNames().length == 1) { nameCheck.has(prop, names.getNames()[0]); } else { nameCheck.has(prop, P.within(names.getNames())); } query.where(nameCheck); } @SuppressWarnings("unchecked") public void visit(Discriminator discriminator, HawkularTraversal<?, ?> query, RelationWith.Ids ids, QueryTranslationState state) { query.restrictTo(discriminator); if (ids.getIds().length == 1) { query.has(__eid.name(), ids.getIds()[0]); } else { query.has(__eid.name(), P.within(ids.getIds())); } } public void visit(Discriminator discriminator, HawkularTraversal<?, ?> query, RelationWith.PropertyValues properties, QueryTranslationState state) { query.restrictTo(discriminator); applyPropertyFilter(discriminator, query, state, properties.getProperty(), properties.getValues()); } public void visit(Discriminator discriminator, HawkularTraversal<?, ?> query, RelationWith.SourceOfType types, QueryTranslationState state) { visit(discriminator, query, types, true, state); } public void visit(Discriminator discriminator, HawkularTraversal<?, ?> query, RelationWith.TargetOfType types, QueryTranslationState state) { visit(discriminator, query, types, false, state); } public void visit(Discriminator discriminator, HawkularTraversal<?, ?> query, RelationWith.SourceOrTargetOfType types, QueryTranslationState state) { visit(discriminator, query, types, null, state); } @SuppressWarnings("unchecked") private void visit(Discriminator discriminator, HawkularTraversal<?, ?> query, RelationWith.SourceOrTargetOfType types, Boolean source, QueryTranslationState state) { HawkularTraversal<?, ?> origQuery = query; origQuery.restrictTo(discriminator); String prop; if (source == null) { query = hwk__().bothV(); prop = __type.name(); } else if (source) { prop = __sourceType.name(); } else { prop = __targetType.name(); } // look ahead if the type of the incidence vertex is of the desired type(s) if (types.getTypes().length == 1) { Constants.Type type = Constants.Type.of(types.getTypes()[0]); query.has(prop, type.name()); } else { String[] typeNames = Stream.of(types.getTypes()).map(t -> Constants.Type.of(t).name()) .toArray(String[]::new); query.has(prop, P.within(typeNames)); } if (source == null) { origQuery.where(query); } } public void visit(Discriminator discriminator, HawkularTraversal<?, ?> query, SwitchElementType filter, QueryTranslationState state) { final boolean jumpFromEdge = filter.isFromEdge(); switch (filter.getDirection()) { case incoming: if (jumpFromEdge) { state.setInEdges(false); state.setComingFrom(null); query.outV(); } else { state.setInEdges(true); state.setComingFrom(Direction.IN); query.inE().restrictTo(discriminator); } break; case outgoing: if (jumpFromEdge) { state.setInEdges(false); state.setComingFrom(null); query.inV(); } else { state.setInEdges(true); state.setComingFrom(Direction.OUT); query.outE().restrictTo(discriminator); } break; case both: if (jumpFromEdge) { state.setInEdges(false); state.setComingFrom(null); query.bothV(); } else { state.setInEdges(true); state.setComingFrom(Direction.BOTH); query.bothE().restrictTo(discriminator); } break; } state.setExplicitChange(true); } public void visit(HawkularTraversal<?, ?> query, NoopFilter filter, QueryTranslationState state) { //nothing to do } public void visit(Discriminator discriminator, HawkularTraversal<?, ?> query, With.PropertyValues filter, QueryTranslationState state) { //if the property is mirrored, we check for its value directly at the edge and only move to the target //vertex afterwards. If it is not mirrored, we first move to the target vertex and then filter by the property boolean propertyMirrored = Constants.Property.isMirroredInEdges(filter.getName()); if (!propertyMirrored) { goBackFromEdges(query, state); query.existsAt(discriminator); } applyPropertyFilter(discriminator, query, state, filter.getName(), filter.getValues()); if (propertyMirrored) { goBackFromEdges(query, state); query.existsAt(discriminator); } } @SuppressWarnings("unchecked") private void applyPropertyFilter(Discriminator discriminator, HawkularTraversal<?, ?> query, QueryTranslationState state, String propertyName, Object... values) { String mappedName = Constants.Property.mapUserDefined(propertyName); boolean checkStateVertex = !state.isInEdges() && !Constants.Type.getIdentityVertexProperties().contains(mappedName); HawkularTraversal<?, ?> check = query; if (checkStateVertex) { check = hwk__().outE(__inState.name()).restrictTo(discriminator).inV(); } boolean checkLabel = state.isInEdges() && "label".equals(mappedName); if (values.length == 0) { if (!checkLabel) { check.has(mappedName); } } else if (values.length == 1) { if (checkLabel) { check.hasLabel(values[0]); } else { check.has(mappedName, values[0]); } } else { if (checkLabel) { check.hasLabel(P.within(values)); } else { check.has(mappedName, P.within(values)); } } if (checkStateVertex) { query.where(check); } } @SuppressWarnings("unchecked") public void visit(Discriminator discriminator, HawkularTraversal<?, ?> query, With.CanonicalPaths filter, QueryTranslationState state) { String prop = chooseBasedOnDirection(__cp, __targetCp, __sourceCp, state.getComingFrom()).name(); if (filter.getPaths().length == 1) { //this only works if we are on vertices, so check for that if (prop.equals(__cp.name())) { query.has(T.label, Constants.Type.of(filter.getPaths()[0].getSegment().getElementType()) .identityVertexLabel()); } query.has(prop, filter.getPaths()[0].toString()); } else { if (prop.equals(__cp.name())) { String[] labels = Stream.of(filter.getPaths()) .map(p -> Constants.Type.of(p.getSegment().getElementType()).identityVertexLabel()) .toArray(String[]::new); query.has(T.label, P.within(labels)); } String[] paths = Stream.of(filter.getPaths()).map(Object::toString).toArray(String[]::new); query.has(prop, P.within(paths)); } goBackFromEdges(query, state); query.existsAt(discriminator); } @SuppressWarnings("unchecked") public <E> void visit(Discriminator discriminator, HawkularTraversal<?, E> query, With.RelativePaths filter, QueryTranslationState state) { goBackFromEdges(query, state); query.existsAt(discriminator); String originLabel = filter.getMarkerLabel(); if (filter.getPaths().length == 1) { if (originLabel != null) { //progress our main query down to the candidates from which we will select the results apply(filter.getPaths()[0].getSegment(), query); query.existsAt(discriminator); String candidateLabel = nextRandomLabel(); query.as(candidateLabel); //create the traversal to a relative path going from the provided marked origin HawkularTraversal<?, ?> relativePath = hwk__().as(originLabel); convertToPipeline(discriminator, filter.getPaths()[0], relativePath); //using the same label makes sure the candidate in query matches the one navigated to by the relative //path relativePath.as(candidateLabel); //extend the query with our match and select the matching candidates query.match(relativePath).select(candidateLabel); } else { convertToPipeline(discriminator, filter.getPaths()[0], query); } } else { if (originLabel != null) { String candidateLabel = nextRandomLabel(); Traversal[] candidates = new GraphTraversal<?, ?>[filter.getPaths().length]; Arrays.setAll(candidates, i -> { GraphTraversal<?, ?> n = __.start(); apply(filter.getPaths()[i].getSegment(), n); return n; }); HawkularTraversal<?, ?>[] relativePaths = new HawkularTraversal<?, ?>[filter.getPaths().length]; Arrays.setAll(relativePaths, i -> { HawkularTraversal<?, ?> rp = hwk__().as(originLabel); convertToPipeline(discriminator, filter.getPaths()[i], rp); rp.as(candidateLabel); return rp; }); query.union(candidates).as(candidateLabel).match(relativePaths).select(candidateLabel); } else { HawkularTraversal[] relativePaths = new HawkularTraversal[filter.getPaths().length]; Arrays.setAll(relativePaths, i -> { HawkularTraversal<?, ?> rp = hwk__(); convertToPipeline(discriminator, filter.getPaths()[i], rp); return rp; }); query.union(relativePaths); } } } public void visit(Discriminator discriminator, HawkularTraversal<?, ?> query, Marker filter, QueryTranslationState state) { goBackFromEdges(query, state); query.existsAt(discriminator); query.as(filter.getLabel()); } @SuppressWarnings("unchecked") public void visit(Discriminator discriminator, HawkularTraversal<?, ?> query, With.DataAt dataPos, QueryTranslationState state) { goBackFromEdges(query, state); query.existsAt(discriminator); query.outE(hasData.name()).restrictTo(discriminator).inV(); for (Path.Segment seg : dataPos.getDataPath().getPath()) { if (SegmentType.up.equals(seg.getElementType())) { query.in(contains.name()); } else { query.out(contains.name()); } query.has(__type.name(), Constants.Type.structuredData.name()); // map members have both index and key (so that the order of the elements is preserved) // list members have only the index Integer index = toInteger(seg.getElementId()); if (index == null) { query.has(Constants.Property.__structuredDataKey.name(), seg.getElementId()); } else { //well, the map could have a numeric key, so we cannot say it has to be a list index here. GraphTraversal<?, ?>[] indexOrKey = new GraphTraversal<?, ?>[2]; indexOrKey[0] = __.has(Constants.Property.__structuredDataIndex.name(), index) .hasNot(Constants.Property.__structuredDataKey.name()); indexOrKey[1] = __.has(Constants.Property.__structuredDataKey.name(), seg.getElementId()); query.or((Traversal[]) indexOrKey); } } } public void visit(HawkularTraversal<?, ?> query, With.DataValued dataValue, QueryTranslationState state) { goBackFromEdges(query, state); Object val = dataValue.getValue(); query.has(Constants.Property.__type.name(), Constants.Type.structuredData.name()); if (val == null) { query.has(Constants.Property.__structuredDataType.name(), StructuredData.Type.undefined.name()); } else { if (Long.class == val.getClass()) { query.has(Constants.Property.__structuredDataValue_i.name(), val); } else if (Boolean.class == val.getClass()) { query.has(Constants.Property.__structuredDataValue_b.name(), val); } else if (Double.class == val.getClass()) { query.has(Constants.Property.__structuredDataValue_f.name(), val); } else { //fallback everything else to string query.has(Constants.Property.__structuredDataValue_s.name(), val.toString()); } } } @SuppressWarnings("unchecked") public void visit(HawkularTraversal<?, ?> query, With.DataOfTypes dataTypes, QueryTranslationState state) { goBackFromEdges(query, state); if (dataTypes.getTypes().length == 1) { query.has(Constants.Property.__structuredDataType.name(), dataTypes.getTypes()[0].name()); } else { String[] types = Stream.of(dataTypes.getTypes()).map(StructuredData.Type::name).toArray(String[]::new); query.has(Constants.Property.__structuredDataType.name(), P.within(types)); } } @SuppressWarnings("unchecked") public void visit(Discriminator discriminator, HawkularTraversal<?, ?> query, RecurseFilter recurseFilter, QueryTranslationState state) { goBackFromEdges(query, state); query.existsAt(discriminator); HawkularTraversal<?, ?> descend = hwk__(); if (recurseFilter.getLoopChains().length == 1) { QueryTranslationState descendState = state.clone(); for (Filter f : recurseFilter.getLoopChains()[0]) { FilterApplicator<?> applicator = FilterApplicator.of(f); applicator.applyTo(discriminator, descend, descendState); } goBackFromEdges(descend, descendState); } else { HawkularTraversal[] pipes = new HawkularTraversal[recurseFilter.getLoopChains().length]; for (int i = 0; i < recurseFilter.getLoopChains().length; ++i) { pipes[i] = hwk__(); QueryTranslationState innerState = state.clone(); for (Filter f : recurseFilter.getLoopChains()[i]) { FilterApplicator<?> applicator = FilterApplicator.of(f); applicator.applyTo(discriminator, pipes[i], innerState); } FilterApplicator.finishPipeline(pipes[i], innerState, state); } descend.union(pipes).dedup(); } query.repeat((Traversal) descend).emit(); } public void visit(Discriminator discriminator, HawkularTraversal<?, ?> query, @SuppressWarnings("UnusedParameters") With.SameIdentityHash filter, QueryTranslationState state) { goBackFromEdges(query, state); query.out(__withIdentityHash.name()).in(__withIdentityHash.name()); query.existsAt(discriminator); } private void convertToPipeline(Discriminator discriminator, RelativePath path, HawkularTraversal<?, ?> pipeline) { for (Path.Segment s : path.getPath()) { if (SegmentType.up.equals(s.getElementType())) { pipeline.inE(contains.name()).restrictTo(discriminator).outV(); pipeline.existsAt(discriminator); } else { Constants.Type targetType = Constants.Type.of(s.getElementType()); pipeline.outE(contains.name()).restrictTo(discriminator) .has(__targetType.name(), targetType.name()) .has(__targetEid.name(), s.getElementId()).inV() .hasLabel(targetType.identityVertexLabel()); pipeline.existsAt(discriminator); } } } private void apply(Path.Segment segment, GraphTraversal<?, ?> pipeline) { pipeline.has(__type.name(), Constants.Type.of(Entity.typeFromSegmentType(segment.getElementType())).name()); pipeline.has(__eid.name(), segment.getElementId()); } /** * A very simplistic conversion of string to positive integer in only decimal radix. * * <p>This is used to figure out whether a segment id represents an index or a key. * * @param str the string potentially representing a number * @return the parsed number or null if the string is not a supported number */ private static Integer toInteger(String str) { char[] chars = str.toCharArray(); int result = 0; int multiplier = 1; for (int i = chars.length - 1; i >= 0; --i, multiplier *= 10) { char c = chars[i]; if ('0' <= c && c <= '9') { result += (c - '0') * multiplier; } else { return null; } } return result; } private static String propertyNameBasedOnState(Constants.Property prop, QueryTranslationState state) { if (!state.isInEdges()) { return prop.name(); } switch (prop) { case __cp: return chooseBasedOnDirection(__cp, __targetCp, __sourceCp, state.getComingFrom()).name(); case __eid: return chooseBasedOnDirection(__eid, __targetEid, __sourceEid, state.getComingFrom()).name(); case __type: return chooseBasedOnDirection(T.label.getAccessor(), __targetType.name(), __sourceType.name(), state.getComingFrom()); default: return prop.name(); } } private static <T> T chooseBasedOnDirection(T defaultvalue, T inValue, T outValue, Direction direction) { if (direction == null) { return defaultvalue; } switch (direction) { case IN: return outValue; case OUT: return inValue; default: throw new IllegalStateException("Properties-on-edges optimization cannot be applied when " + "following both directions. This is probably a bug in the query translation."); } } private static void goBackFromEdges(GraphTraversal<?, ?> query, QueryTranslationState state) { if (state.isInEdges()) { switch (state.getComingFrom()) { case IN: query.outV(); break; case OUT: query.inV(); } state.setInEdges(false); state.setComingFrom(null); } } private static String nextRandomLabel() { return "label-" + CNT.getAndIncrement(); } }