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

com.redhat.lightblue.metadata.CompositeMetadata Maven / Gradle / Ivy

There is a newer version: 2.18.0
Show newest version
/*
 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.metadata;

import com.redhat.lightblue.query.*;
import com.redhat.lightblue.util.Error;
import com.redhat.lightblue.util.MutablePath;
import com.redhat.lightblue.util.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * Composite metedata is a directed tree. The requested entity is at the root of
 * the composite metadata. Every entity arrived by following an association is
 * another node in the composite metadata, and the edge points to the
 * destination of the association.
 *
 * Composite metadata extends EntityMetadata with these functions:
 * 
    *
  • The tree structure of entities are visible through CompositeMetadata
  • *
  • Reference fields are extended to include the projected fields from the * associated entities
  • *
* * Composite metadata needs to be computed for every request. The computation * takes into account the request queries and projections to determine how deep * the reference tree needs to be traversed. */ public class CompositeMetadata extends EntityMetadata { private static final Logger LOGGER = LoggerFactory.getLogger(CompositeMetadata.class); private final Path entityPath; private final CompositeMetadata parent; private final Map children = new HashMap<>(); /** * Interface that returns an instance of entity metadata given the entity * name, version, and the field into which entity needs to be injected. The * logic to include or exclude the entity based on the projection, query, * and sort requirements is implemented by this class. If an instance of * entity metadata is not to be included in the composite metadata, the * getMetadata method must return null. * * If a particular version of a requested entity has references, the * getMetadata method should return an instance of EntityMetadata, not * CompositeMetadata. The caller will recursively descend into all the * entities retrieved and construct the top-level composite metadata. * * If metadata cannot be retrieved, this call should throw an exception. * Returning null means the particular entity metadata is not projected. */ public static interface GetMetadata { public EntityMetadata getMetadata(Path injectionField, String entityName, String version); } /** * Construct a composite metadata using the given entity info and composite * schema. This constructor is to construct a CompositeMetadata at the root * of an entity tree. */ public CompositeMetadata(EntityInfo info, CompositeSchema schema) { this(info, schema, Path.EMPTY, null); } public CompositeMetadata(EntityInfo info, CompositeSchema schema, Path path, CompositeMetadata parent) { super(info, schema); this.entityPath = path; this.parent = parent; } /** * If this composite metadata is the root of an entity metadata tree, * returns empty path. Otherwise, this composite metadata is a descendant of * another composite metadata, and this call returns the full path to the * reference field containing this metadata. */ public Path getEntityPath() { return entityPath; } /** * Returns if this is a simple metadata, one that has no children */ public boolean isSimple() { return children.isEmpty(); } /** * If this composite metadata is the root of an entity metadata tree, * returns null. Otherwise, returns the metadata containing this metadata. */ public CompositeMetadata getParent() { return parent; } /** * Returns a direct child metadata of this metadata. * * @param entityPath The absolute path to the field containing the requested * child */ public CompositeMetadata getChildMetadata(Path entityPath) { ResolvedReferenceField rf = children.get(entityPath); return rf == null ? null : rf.getReferencedMetadata(); } /** * Returns a descendant resolved reference of this metadata * * @param entityPath The absolute path to the field containing the requested * child */ public ResolvedReferenceField getDescendantReference(Path entityPath) { ResolvedReferenceField rf = getChildReference(entityPath); if (rf == null) { for (Map.Entry entry : children.entrySet()) { rf = entry.getValue().getReferencedMetadata().getDescendantReference(entityPath); if (rf != null) { break; } } } return rf; } /** * Returns descendant of this metadata * * @param entityPath The absolute path to the field containing the requested * child */ public CompositeMetadata getDescendantMetadata(Path entityPath) { ResolvedReferenceField rf = getDescendantReference(entityPath); return rf == null ? null : rf.getReferencedMetadata(); } /** * Returns a direct child resolved reference of this metadata. * * @param entityPath The absolute path to the field containing the requested * child */ public ResolvedReferenceField getChildReference(Path entityPath) { return children.get(entityPath); } /** * Returns the absolute paths of the direct children of this metadata */ public Set getChildPaths() { return children.keySet(); } /** * Returns the entity tree structure as a string */ public String toTreeString() { StringBuilder bld = new StringBuilder(); toTreeString(0, bld); return bld.toString(); } @Override public String toString() { return toTreeString(); } /** * Returns the composite metadata containing the field pointed by the given * path */ public CompositeMetadata getEntityOfPath(Path path) { // Resolve the path FieldTreeNode node = resolve(path); // Find the entity of the node return getEntityOfField(node); } /** * Returns the composite metadata containing the field */ public CompositeMetadata getEntityOfField(FieldTreeNode field) { if (field != null) { ResolvedReferenceField rr = getResolvedReferenceOfField(field); if (rr == null) { return this; } else { return rr.getReferencedMetadata(); } } else { return null; } } /** * Returns the resolved reference containing the field */ public ResolvedReferenceField getResolvedReferenceOfField(Path field) { return getResolvedReferenceOfField(resolve(field)); } /** * Returns the resolved reference containing the field */ public ResolvedReferenceField getResolvedReferenceOfField(FieldTreeNode field) { ResolvedReferenceField ret = null; if (field != null) { do { if (field instanceof ResolvedReferenceField) { ret = (ResolvedReferenceField) field; } else { field = field.getParent(); } } while (field != null && ret == null); } return ret; } /** * Returns the field name for the given field node relative to the entity it * is contained in */ public Path getEntityRelativeFieldName(FieldTreeNode fieldNode) { return getEntitySchema().getEntityRelativeFieldName(fieldNode); } /** * Builds a composite metadata rooted at the given entity metadata. */ public static CompositeMetadata buildCompositeMetadata(EntityMetadata root, GetMetadata gmd) { LOGGER.debug("enter buildCompositeMetadata"); Error.push("compositeMetadata"); try { CompositeMetadata cmd = buildCompositeMetadata(root, gmd, new Path(), null, new MutablePath()); return cmd; } finally { LOGGER.debug("end buildCompositeMetadata"); Error.pop(); } } /** * * @param root the root metadata * @param gmd the GetMetadata for resolving metadata * @param entityPath the path to the metadata * @param parentEntity the parent metadata * @param path relative path in processing, if not a recursive call it * should be a new empty MutablePath object * @return */ private static CompositeMetadata buildCompositeMetadata(EntityMetadata root, GetMetadata gmd, Path entityPath, CompositeMetadata parentEntity, MutablePath path) { // Recursively process and copy the fields, retrieving // metadata for references Error.push(root.getName()); try { CompositeSchema cschema = CompositeSchema.newSchemaWithEmptyFields(root.getEntitySchema()); CompositeMetadata cmd = new CompositeMetadata(root.getEntityInfo(), cschema, entityPath, parentEntity); // copy fields, resolve references copyFields(cschema.getFields(), root.getEntitySchema().getFields(), path, cmd, gmd); return cmd; } finally { Error.pop(); } } /** * QueryIterator implementation that creates new instances of * QueryExpression implementations when the rewritten absolute path is not * used in the query already. */ private static final class AbsRewriteItr extends QueryIterator { /** * we'll interpret field names with respect to this entity */ private FieldTreeNode interpretWRTEntity; public AbsRewriteItr(FieldTreeNode root) { interpretWRTEntity = root; } @Override protected QueryExpression itrValueComparisonExpression(ValueComparisonExpression q, Path context) { Path p = rewrite(context, q.getField()); if (!p.equals(q.getField())) { return new ValueComparisonExpression(p, q.getOp(), q.getRvalue()); } else { return q; } } @Override protected QueryExpression itrRegexMatchExpression(RegexMatchExpression q, Path context) { Path p = rewrite(context, q.getField()); if (!p.equals(q.getField())) { return new RegexMatchExpression(p, q.getRegex(), q.isCaseInsensitive(), q.isMultiline(), q.isExtended(), q.isDotAll()); } else { return q; } } @Override protected QueryExpression itrFieldComparisonExpression(FieldComparisonExpression q, Path context) { Path left = rewrite(context, q.getField()); Path right = rewrite(context, q.getRfield()); if (!left.equals(q.getField()) || !right.equals(q.getRfield())) { return new FieldComparisonExpression(left, q.getOp(), right); } else { return q; } } @Override protected QueryExpression itrArrayContainsExpression(ArrayContainsExpression q, Path context) { Path p = rewrite(context, q.getArray()); if (!p.equals(q.getArray())) { return new ArrayContainsExpression(p, q.getOp(), q.getValues()); } else { return q; } } @Override protected QueryExpression itrArrayMatchExpression(ArrayMatchExpression q, Path context) { Path p = rewrite(context, q.getArray()); if (!p.equals(q.getArray())) { return new ArrayMatchExpression(p, q.getElemMatch()); } else { return q; } } private Path rewrite(Path context, Path field) { LOGGER.debug("rewriting {}", field); if (context != null && !Path.EMPTY.equals(context)) { throw Error.get(MetadataConstants.ERR_INVALID_CONTEXT, "Expected empty path, got: " + context.toString()); } // We interpret field name with respect to the current entity FieldTreeNode fieldNode = interpretWRTEntity.resolve(field); Path absFieldPath = fieldNode.getFullPath(); LOGGER.debug("Field full path={}", absFieldPath); return absFieldPath; } } /** * Copy fields from source to dest. * * @param dest where to write copied fields * @param source source for fields to copy * @param path relative path in processing * @param parentEntity the parent metadata * @param gmd impl of GetMetadata for finding metadata for a given field */ private static void copyFields(Fields dest, Fields source, MutablePath path, CompositeMetadata parentEntity, GetMetadata gmd) { // Iterate over source fields. // If field is simple // shallow copy SimpleField // add result to dest // else if field is object // shallow copy new ObjectField // recursively call copyFields on the new ObjectField to copy object fields // add result to dest // else if field is array // if elements are objects call copyFields on a new ObjectArrayElement // else create new SimpleArrayElement // shallow copy to a new ArrayField created with *Element created above // add result to dest // else (field is a reference) // resolve ResolvedReferenceField // if found, add result to dest // copy all properties from source to dest for (Iterator itr = source.getFields(); itr.hasNext();) { Field field = itr.next(); Error.push(field.getName()); path.push(field.getName()); // push even for simple field since it won't matter in that case LOGGER.debug("Processing {}", path); try { if (field instanceof SimpleField) { SimpleField newField = new SimpleField(field.getName(), field.getType()); newField.shallowCopyFrom(field); dest.put(newField); } else if (field instanceof ObjectField) { ObjectField newField = new ObjectField(field.getName()); newField.shallowCopyFrom(field); copyFields(newField.getFields(), ((ObjectField) field).getFields(), path, parentEntity, gmd); dest.put(newField); } else if (field instanceof ArrayField) { ArrayElement sourceEl = ((ArrayField) field).getElement(); ArrayElement newElement; if (sourceEl instanceof ObjectArrayElement) { path.push(Path.ANY); // Need to copy an Object array, there is a Fields object in it newElement = new ObjectArrayElement(); copyFields(((ObjectArrayElement) newElement).getFields(), ((ObjectArrayElement) sourceEl).getFields(), path, parentEntity, gmd); path.pop(); } else { SimpleArrayElement simpleElement=new SimpleArrayElement(((SimpleArrayElement) sourceEl).getType()); simpleElement.setConstraints(((SimpleArrayElement)sourceEl).getConstraints()); LOGGER.debug("Constraints:{}",simpleElement.getConstraints()); newElement=simpleElement; } newElement.getProperties().putAll(sourceEl.getProperties()); ArrayField newField = new ArrayField(field.getName(), newElement); newField.shallowCopyFrom(field); dest.put(newField); } else { // Field is a reference ReferenceField reference = (ReferenceField) field; ResolvedReferenceField newField = resolveReference(reference, path, parentEntity, gmd); if (newField != null) { dest.put(newField); } } } finally { Error.pop(); } path.pop(); } } private static ResolvedReferenceField resolveReference(ReferenceField source, MutablePath path, CompositeMetadata parentEntity, GetMetadata gmd) { LOGGER.debug("resolveReference {}:{}", path, source); EntityMetadata md = gmd.getMetadata(path.immutableCopy(), source.getEntityName(), source.getVersionValue()); // If metadata is null, the entity is not projected, so we // don't even set it in the containing Fields. If somehow the // GetMetadata cannot retrieve the metadata, it throws an // exception. if (md != null) { LOGGER.debug("resolved"); // We have the entity metadata. We insert this as a // resolved reference Path fpath = path.immutableCopy(); path.push(Path.ANY); CompositeMetadata cmd = buildCompositeMetadata(md, gmd, fpath, parentEntity, path); path.pop(); ResolvedReferenceField newField = new ResolvedReferenceField(source, md, cmd); parentEntity.children.put(fpath, newField); return newField; } else { LOGGER.debug("Not resolved"); } return null; } private void toTreeString(int depth, StringBuilder bld) { for (int i = 0; i < depth; i++) { bld.append(" "); } bld.append(getName()).append(':').append(entityPath.toString()).append('\n'); for (ResolvedReferenceField ch : children.values()) { ch.getReferencedMetadata().toTreeString(depth + 1, bld); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy