All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.redhat.lightblue.query.Projection Maven / Gradle / Ivy

/*
 Copyright 2013 Red Hat, Inc. and/or its affiliates.

 This file is part of lightblue.

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see .
 */
package com.redhat.lightblue.query;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Joiner;
import com.redhat.lightblue.util.JsonObject;
import com.redhat.lightblue.util.MutablePath;
import com.redhat.lightblue.util.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;

/**
 * Base class for all projection objects
 */
public abstract class Projection extends JsonObject {
    private static final long serialVersionUID = 1L;

    private static final Logger LOGGER = LoggerFactory.getLogger(Projection.class);

    /**
     * Inclusion status for a field
     *
     * 
    *
  • explicit_exclusion: the projection excludes this field * explicitly
  • *
  • implicit_exzclusion: the projection excludes this field because an * ancestor of the field is excluded
  • *
  • explicit_inclusion: the projection includes the field explicitly
  • *
  • explicit_exclusion: the projection excludes the field explicitly
  • *
  • undecided: the projection does not decide if the field should be * included/excluded, and does not reference it
  • *
      */ public static enum Inclusion { explicit_exclusion, implicit_exclusion, explicit_inclusion, implicit_inclusion, undecided }; public static Projection fromJson(JsonNode node) { if (node instanceof ArrayNode) { return ProjectionList.fromJson((ArrayNode) node); } else { return BasicProjection.fromJson((ObjectNode) node); } } /** * Adds two projections and returns a new projection containing both. Any * projection can be null. If the resulting projection is empty, returns * null. */ public static Projection add(Projection p1, Projection p2) { List list = new ArrayList<>(); if (p1 instanceof ProjectionList) { list.addAll(((ProjectionList) p1).getItems()); } else if (p1 != null) { list.add(p1); } if (p2 instanceof ProjectionList) { list.addAll(((ProjectionList) p2).getItems()); } else if (p2 != null) { list.add(p2); } return list.isEmpty() ? null : new ProjectionList(list); } /** * Returns whether to include/exclude the field based on whether the field * matches the pattern * * @param field The field name * @param pattern The projection pattern * @param inclusion flag to return if the field matches the given pattern * @return value of inclusion if field matches the pattern, * else null * *
           *     field    pattern   result
           *      a          *       true
           *      a          a       true
           *      a          b       false
           *      a.b        *       false
           *      a.b       *.b      true
           *      a.b       a.*      true
           *      a.b       *.*      true
           *      a.b       a.b      true
           * 
      * * The return value is inclusion if result is true, * null if result is false, meaning that whether the field * should be included or not cannot be decided. */ public static Boolean fieldMatchesPattern(Path field, Path pattern, boolean inclusion) { if (field.matches(pattern)) { return inclusion; } else { return null; } } /** * If the field is an ancestor of the pattern, and if inclusion * is true, returns true. Otherwise, returns null, meaning that whether the * field is included or not cannot be decided. * * @param field The field name * @param pattern The projection pattern * @param inclusion flag to return if the field matches the given pattern * @return TRUE if field is ancestor of the pattern and * inclusion is true, else null */ public static Boolean fieldAncestorOfPattern(Path field, Path pattern, boolean inclusion) { if (field.matchingPrefix(pattern)) { if (inclusion) { return Boolean.TRUE; } else { return null; } } else { return null; } } /** * Returns if the field should be included based on the recursive pattern. * * @param field The field name * @param pattern The projection pattern * @param inclusion flag to return if the field matches the given pattern * @return Boolean value of inclusion if field is in subtree of * pattern else null */ public static Boolean impliedInclusion(Path field, Path pattern, boolean inclusion) { if (field.numSegments() > pattern.numSegments() && // If we're checking a field deeper than the pattern // And if we're checking a field under the subtree of the pattern field.prefix(pattern.numSegments()).matches(pattern)) { return inclusion ? Boolean.TRUE : Boolean.FALSE; } else { return null; } } /** * Returns if the field should be included based on the pattern given. * * @param field The field whose inclusion is to be decided * @param pattern The field pattern of projection * @param inclusion If the projection expression includes the field. If * false, the projection is for exclusion * @param recursive If the projection is recursive * * @return implicit or explicit inclusion/exclusion, or undecided if * projection does not decide if the field should be included/excluded */ public static Inclusion isFieldIncluded(Path field, Path pattern, boolean inclusion, boolean recursive) { // field match first, most specific type of check Boolean v = fieldMatchesPattern(field, pattern, inclusion); if (v != null) { // if the last segment is ANY then inclusion/exclusion is implicit. else, inclusion/exclusion is explicit if (pattern.tail(0).equals(Path.ANY)) { return v ? Inclusion.implicit_inclusion : Inclusion.implicit_exclusion; } else { return v ? Inclusion.explicit_inclusion : Inclusion.explicit_exclusion; } } // check field ancestor v = fieldAncestorOfPattern(field, pattern, inclusion); if (v != null) { // is always explicit return v ? Inclusion.explicit_inclusion : Inclusion.explicit_exclusion; } // if recursive, check for implied inclusion/exclusion if (recursive) { v = impliedInclusion(field, pattern, inclusion); if (v != null) { // recursive is always implicit return v ? Inclusion.implicit_inclusion : Inclusion.implicit_exclusion; } } return Inclusion.undecided; } /** * Determines if the field is included in this projection * * @param field The absolute name of the field * * If the field name contains array indexes, they are converted to '*' * before evaluation. */ public Inclusion getFieldInclusion(Path field) { LOGGER.debug("Checking if {} is projected", field); return getFieldInclusion(field, Path.EMPTY); } /** * Determine if the field is explicitly included/excluded, implicitly * included, or the projection does not decide on the field. */ public Inclusion getFieldInclusion(Path field, Path ctx) { Path mfield = toMask(field); ctx = toMask(ctx); if (this instanceof FieldProjection) { return getFieldInclusion(mfield, (FieldProjection) this, ctx); } else if (this instanceof ArrayProjection) { return getFieldInclusion(mfield, (ArrayProjection) this, ctx); } else if (this instanceof ProjectionList) { return getFieldInclusion(mfield, (ProjectionList) this, ctx); } return Inclusion.undecided; } /** * Returns true if the field is needed to evaluate the projection */ public boolean isFieldRequiredToEvaluateProjection(Path field) { LOGGER.debug("Checking if {} is referenced in projection", field); return isFieldRequiredToEvaluateProjection(field, Path.EMPTY); } /** * Returns true if the field is needed to evaluate the projection */ public boolean isFieldRequiredToEvaluateProjection(Path field, Path ctx) { Path mfield = toMask(field); ctx = toMask(ctx); if (this instanceof FieldProjection) { switch (getFieldInclusion(mfield, (FieldProjection) this, ctx)) { case implicit_inclusion: case explicit_inclusion: return true; default: return false; } } else if (this instanceof ArrayQueryMatchProjection) { if (getFieldInclusion(mfield, (ArrayProjection) this, ctx) == Inclusion.undecided) { LOGGER.debug("whether to include {} is Undecided, checking projection query", mfield); Path absField = new Path(ctx, toMask(((ArrayQueryMatchProjection) this).getField())); Path nestedCtx = new Path(absField, Path.ANYPATH); boolean ret = ((ArrayQueryMatchProjection) this).getMatch().isRequired(field, nestedCtx); LOGGER.debug("isRequired({},{}.*={}", field, absField, ret); if (ret) { return true; } LOGGER.debug("Query does not require {}, checking nested projection", mfield); if (((ArrayProjection) this).getProject() != null) { ret = ((ArrayProjection) this).getProject().isFieldRequiredToEvaluateProjection(field, nestedCtx); } LOGGER.debug("result:{}", ret); return ret; } else { return true; } } else if (this instanceof ArrayRangeProjection) { return getFieldInclusion(mfield, (ArrayProjection) this, ctx) != Inclusion.undecided; } else if (this instanceof ProjectionList) { for (Projection x : ((ProjectionList) this).getItems()) { if (x.isFieldRequiredToEvaluateProjection(field, ctx)) { return true; } } } return false; } private Inclusion getFieldInclusion(Path field, ArrayProjection p, Path context) { Path absField = new Path(context, toMask(p.getField())); LOGGER.debug("Checking if array projection on {} projects {}", absField, field); Inclusion inc = isFieldIncluded(field, absField, p.isInclude(), false); Inclusion inc2 = p.getProject().getFieldInclusion(field, new Path(absField, Path.ANYPATH)); Inclusion ret; if (inc == Inclusion.explicit_inclusion || inc2 == Inclusion.explicit_inclusion) { ret = Inclusion.explicit_inclusion; } else if (inc == Inclusion.implicit_inclusion || inc2 == Inclusion.implicit_inclusion) { ret = Inclusion.implicit_inclusion; } else if (inc == Inclusion.explicit_exclusion || inc2 == Inclusion.explicit_exclusion) { ret = Inclusion.explicit_exclusion; } else if (inc == Inclusion.implicit_exclusion || inc2 == Inclusion.implicit_exclusion) { ret = Inclusion.implicit_exclusion; } else { ret = Inclusion.undecided; } LOGGER.debug("array projection on {} projects {}: {}", absField, field, ret); return ret; } private Inclusion getFieldInclusion(Path field, ProjectionList p, Path context) { LOGGER.debug("Checking if a projection list projects {}", field); Inclusion lastResult = Inclusion.undecided; List items = p.getItems(); ListIterator itemsItr = items.listIterator(items.size()); while (itemsItr.hasPrevious()) { Inclusion ret = itemsItr.previous().getFieldInclusion(field, context); if (ret != Inclusion.undecided) { lastResult = ret; break; } } LOGGER.debug("Projection list projects {}: {}", field, lastResult); return lastResult; } private Inclusion getFieldInclusion(Path field, FieldProjection p, Path context) { Path projectionField = new Path(context, toMask(p.getField())); LOGGER.debug("Checking if field projection on {} projects {}", projectionField, field); Inclusion inc = isFieldIncluded(field, projectionField, p.isInclude(), p.isRecursive()); LOGGER.debug("Field projection on {} projects {}: {}", projectionField, field, inc); return inc; } /** * If a path includes array indexes, change the indexes into ANY */ private static Path toMask(Path p) { int n = p.numSegments(); MutablePath mp = null; for (int i = 0; i < n; i++) { if (p.isIndex(i)) { if (mp == null) { mp = p.mutableCopy(); } mp.set(i, Path.ANY); } } return mp == null ? p : mp.immutableCopy(); } protected static Path getNonRelativePath(Path p) { List segments = new ArrayList<>(); int numberOfParentsOnPath = 0; for (int i = p.numSegments() - 1; i >= 0; i--) { if (Path.THIS.equals(p.head(i))) { continue; } else if (Path.PARENT.equals(p.head(i))) { numberOfParentsOnPath++; } else { if (numberOfParentsOnPath > 0) { numberOfParentsOnPath--; continue; } segments.add(p.head(i)); } } Collections.reverse(segments); return new Path(Joiner.on(".").join(segments)); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy