blob: befc705ccbf42ee1642c046a746ae21644ad8d92 [file] [log] [blame]
/**
* 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
*
* 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.apache.atlas.typesystem.types;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.UnmodifiableIterator;
import org.apache.atlas.AtlasException;
import org.apache.atlas.typesystem.IStruct;
import org.apache.atlas.typesystem.persistence.DownCastStructInstance;
import org.apache.atlas.typesystem.types.TypeUtils.Pair;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
/**
* Represents a Type that can have SuperTypes. An Instance of the HierarchicalType can be
* downcast to a SuperType.
* @param <ST> the Type of the SuperType. TraitTypes have TraitTypes as SuperTypes, ClassTypes
* have ClassTypes
* as SuperTypes.
* @param <T> the class of the Instance of this DataType.
*/
public abstract class HierarchicalType<ST extends HierarchicalType, T> extends AbstractDataType<T> {
public final TypeSystem typeSystem;
public final Class<ST> superTypeClass;
public final FieldMapping fieldMapping;
public final int numFields;
public final ImmutableSet<String> superTypes;
public final ImmutableList<AttributeInfo> immediateAttrs;
public final ImmutableMap<String, String> attributeNameToType;
protected ImmutableMap<String, List<Path>> superTypePaths;
protected ImmutableMap<String, Path> pathNameToPathMap;
HierarchicalType(TypeSystem typeSystem, Class<ST> superTypeClass, String name, ImmutableSet<String> superTypes,
int numFields) {
this(typeSystem, superTypeClass, name, null, superTypes, numFields);
}
/**
* Used when creating a Type, to support recursive Structs.
*/
HierarchicalType(TypeSystem typeSystem, Class<ST> superTypeClass, String name, String description, ImmutableSet<String> superTypes,
int numFields) {
super(name, description);
this.typeSystem = typeSystem;
this.superTypeClass = superTypeClass;
this.fieldMapping = null;
this.numFields = numFields;
this.superTypes = superTypes;
this.immediateAttrs = ImmutableList.of();
this.attributeNameToType = null;
}
HierarchicalType(TypeSystem typeSystem, Class<ST> superTypeClass, String name, ImmutableSet<String> superTypes,
AttributeInfo... fields) throws AtlasException {
this(typeSystem, superTypeClass, name, null, superTypes, fields);
}
HierarchicalType(TypeSystem typeSystem, Class<ST> superTypeClass, String name, String description, ImmutableSet<String> superTypes,
AttributeInfo... fields) throws AtlasException {
super(name, description);
this.typeSystem = typeSystem;
this.superTypeClass = superTypeClass;
Pair<FieldMapping, ImmutableMap<String, String>> p = constructFieldMapping(superTypes, fields);
this.fieldMapping = p.left;
this.attributeNameToType = p.right;
this.numFields = this.fieldMapping.fields.size();
this.superTypes = superTypes == null ? ImmutableSet.<String>of() : superTypes;
this.immediateAttrs = ImmutableList.copyOf(fields);
}
public FieldMapping fieldMapping() {
return fieldMapping;
}
/**
* Given type must be a SubType of this type.
* @param typeName
* @throws AtlasException
*/
public boolean isSubType(String typeName) throws AtlasException {
HierarchicalType cType = typeSystem.getDataType(HierarchicalType.class, typeName);
return (cType == this || cType.superTypePaths.containsKey(getName()));
}
/**
* Validate that current definition can be updated with the new definition
* @param newType
* @return true if the current definition can be updated with the new definition, else false
*/
@Override
public void validateUpdate(IDataType newType) throws TypeUpdateException {
super.validateUpdate(newType);
HierarchicalType newHierarchicalType = (HierarchicalType) newType;
//validate on supertypes
if ((newHierarchicalType.superTypes.size() != superTypes.size())
|| !newHierarchicalType.superTypes.containsAll(superTypes)) {
throw new TypeUpdateException(newType, "New type cannot modify superTypes");
}
//validate on fields
try {
TypeUtils.validateUpdate(fieldMapping, newHierarchicalType.fieldMapping);
} catch (TypeUpdateException e) {
throw new TypeUpdateException(newType, e);
}
}
protected void setupSuperTypesGraph() throws AtlasException {
setupSuperTypesGraph(superTypes);
}
private void setupSuperTypesGraph(ImmutableSet<String> superTypes) throws AtlasException {
Map<String, List<Path>> superTypePaths = new HashMap<String, List<Path>>();
Map<String, Path> pathNameToPathMap = new HashMap<String, Path>();
Queue<Path> queue = new LinkedList<Path>();
queue.add(new Node(getName()));
while (!queue.isEmpty()) {
Path currentPath = queue.poll();
ST superType = currentPath.typeName == getName() ? (ST) this :
(ST) typeSystem.getDataType(superTypeClass, currentPath.typeName);
pathNameToPathMap.put(currentPath.pathName, currentPath);
if (superType != this) {
List<Path> typePaths = superTypePaths.get(superType.getName());
if (typePaths == null) {
typePaths = new ArrayList<Path>();
superTypePaths.put(superType.getName(), typePaths);
}
typePaths.add(currentPath);
}
ImmutableSet<String> sTs = superType == this ? superTypes : superType.superTypes;
if (sTs != null) {
for (String sT : sTs) {
queue.add(new Path(sT, currentPath));
}
}
}
this.superTypePaths = ImmutableMap.copyOf(superTypePaths);
this.pathNameToPathMap = ImmutableMap.copyOf(pathNameToPathMap);
}
protected Pair<FieldMapping, ImmutableMap<String, String>> constructFieldMapping(ImmutableSet<String> superTypes,
AttributeInfo... fields) throws AtlasException {
Map<String, AttributeInfo> fieldsMap = new LinkedHashMap();
Map<String, Integer> fieldPos = new HashMap();
Map<String, Integer> fieldNullPos = new HashMap();
Map<String, String> attributeNameToType = new HashMap<>();
int numBools = 0;
int numBytes = 0;
int numShorts = 0;
int numInts = 0;
int numLongs = 0;
int numFloats = 0;
int numDoubles = 0;
int numBigInts = 0;
int numBigDecimals = 0;
int numDates = 0;
int numStrings = 0;
int numArrays = 0;
int numMaps = 0;
int numStructs = 0;
int numReferenceables = 0;
setupSuperTypesGraph(superTypes);
Iterator<Path> pathItr = pathIterator();
while (pathItr.hasNext()) {
Path currentPath = pathItr.next();
ST superType = currentPath.typeName == getName() ? (ST) this :
(ST) typeSystem.getDataType(superTypeClass, currentPath.typeName);
ImmutableList<AttributeInfo> superTypeFields =
superType == this ? ImmutableList.copyOf(fields) : superType.immediateAttrs;
Set<String> immediateFields = new HashSet<String>();
for (AttributeInfo i : superTypeFields) {
if (superType == this) {
if (immediateFields.contains(i.name)) {
throw new AtlasException(String.format(
"Struct defintion cannot contain multiple fields with the" + " same name %s", i.name));
}
immediateFields.add(i.name);
}
String attrName = i.name;
if (fieldsMap.containsKey(attrName)) {
attrName = currentPath.addOverrideAttr(attrName);
}
attributeNameToType.put(attrName, superType.getName());
fieldsMap.put(attrName, i);
fieldNullPos.put(attrName, fieldNullPos.size());
if (i.dataType() == DataTypes.BOOLEAN_TYPE) {
fieldPos.put(attrName, numBools);
numBools++;
} else if (i.dataType() == DataTypes.BYTE_TYPE) {
fieldPos.put(attrName, numBytes);
numBytes++;
} else if (i.dataType() == DataTypes.SHORT_TYPE) {
fieldPos.put(attrName, numShorts);
numShorts++;
} else if (i.dataType() == DataTypes.INT_TYPE) {
fieldPos.put(attrName, numInts);
numInts++;
} else if (i.dataType() == DataTypes.LONG_TYPE) {
fieldPos.put(attrName, numLongs);
numLongs++;
} else if (i.dataType() == DataTypes.FLOAT_TYPE) {
fieldPos.put(attrName, numFloats);
numFloats++;
} else if (i.dataType() == DataTypes.DOUBLE_TYPE) {
fieldPos.put(attrName, numDoubles);
numDoubles++;
} else if (i.dataType() == DataTypes.BIGINTEGER_TYPE) {
fieldPos.put(attrName, numBigInts);
numBigInts++;
} else if (i.dataType() == DataTypes.BIGDECIMAL_TYPE) {
fieldPos.put(attrName, numBigDecimals);
numBigDecimals++;
} else if (i.dataType() == DataTypes.DATE_TYPE) {
fieldPos.put(attrName, numDates);
numDates++;
} else if (i.dataType() == DataTypes.STRING_TYPE) {
fieldPos.put(attrName, numStrings);
numStrings++;
} else if (i.dataType().getTypeCategory() == DataTypes.TypeCategory.ENUM) {
fieldPos.put(i.name, numInts);
numInts++;
} else if (i.dataType().getTypeCategory() == DataTypes.TypeCategory.ARRAY) {
fieldPos.put(attrName, numArrays);
numArrays++;
} else if (i.dataType().getTypeCategory() == DataTypes.TypeCategory.MAP) {
fieldPos.put(attrName, numMaps);
numMaps++;
} else if (i.dataType().getTypeCategory() == DataTypes.TypeCategory.STRUCT
|| i.dataType().getTypeCategory() == DataTypes.TypeCategory.TRAIT) {
fieldPos.put(attrName, numStructs);
numStructs++;
} else if (i.dataType().getTypeCategory() == DataTypes.TypeCategory.CLASS) {
fieldPos.put(attrName, numReferenceables);
numReferenceables++;
} else {
throw new AtlasException(String.format("Unknown datatype %s", i.dataType()));
}
}
}
this.superTypePaths = ImmutableMap.copyOf(superTypePaths);
this.pathNameToPathMap = ImmutableMap.copyOf(pathNameToPathMap);
FieldMapping fm =
new FieldMapping(fieldsMap, fieldPos, fieldNullPos, numBools, numBytes, numShorts, numInts, numLongs,
numFloats, numDoubles, numBigInts, numBigDecimals, numDates, numStrings, numArrays, numMaps,
numStructs, numReferenceables);
return new Pair(fm, ImmutableMap.copyOf(attributeNameToType));
}
public IStruct castAs(IStruct s, String superTypeName) throws AtlasException {
if (!superTypePaths.containsKey(superTypeName)) {
throw new AtlasException(String.format("Cannot downcast to %s from type %s", superTypeName, getName()));
}
if (s != null) {
if (s.getTypeName() != getName()) {
throw new AtlasException(
String.format("Downcast called on wrong type %s, instance type is %s", getName(),
s.getTypeName()));
}
List<Path> pathToSuper = superTypePaths.get(superTypeName);
if (pathToSuper.size() > 1) {
throw new AtlasException(String.format(
"Cannot downcast called to %s, from %s: there are multiple paths " + "to SuperType",
superTypeName, getName()));
}
ST superType = (ST) typeSystem.getDataType(superTypeClass, superTypeName);
Map<String, String> downCastMap = superType.constructDowncastFieldMap(this, pathToSuper.get(0));
return new DownCastStructInstance(superTypeName, new DownCastFieldMapping(ImmutableMap.copyOf(downCastMap)),
s);
}
return null;
}
public ST getDefinedType(String attrName) throws AtlasException {
if (!attributeNameToType.containsKey(attrName)) {
throw new AtlasException(String.format("Unknown attribute %s in type %s", attrName, getName()));
}
return typeSystem.getDataType(superTypeClass, attributeNameToType.get(attrName));
}
public String getDefinedTypeName(String attrName) throws AtlasException {
return getDefinedType(attrName).getName();
}
public String getQualifiedName(String attrName) throws AtlasException {
String attrTypeName = getDefinedTypeName(attrName);
return attrName.contains(".") ? attrName : String.format("%s.%s", attrTypeName, attrName);
}
protected Map<String, String> constructDowncastFieldMap(ST subType, Path pathToSubType) {
String pathToSubTypeName = pathToSubType.pathAfterThis;
/*
* the downcastMap;
*/
Map<String, String> dCMap = new HashMap<String, String>();
Iterator<Path> itr = pathIterator();
while (itr.hasNext()) {
Path p = itr.next();
Path pInSubType = (Path) subType.pathNameToPathMap.get(p.pathName + "." + pathToSubTypeName);
if (pInSubType.hiddenAttributeMap != null) {
for (Map.Entry<String, String> e : pInSubType.hiddenAttributeMap.entrySet()) {
String mappedInThisType =
p.hiddenAttributeMap != null ? p.hiddenAttributeMap.get(e.getKey()) : null;
if (mappedInThisType == null) {
dCMap.put(e.getKey(), e.getValue());
} else {
dCMap.put(mappedInThisType, e.getValue());
}
}
}
}
return dCMap;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
try {
output(buf, new HashSet<String>());
}
catch (AtlasException e) {
throw new RuntimeException(e);
}
return buf.toString();
}
@Override
public void output(Appendable buf, Set<String> typesInProcess) throws AtlasException {
if (typesInProcess == null) {
typesInProcess = new HashSet<>();
}
else if (typesInProcess.contains(name)) {
// Avoid infinite recursion on bi-directional reference attributes.
try {
buf.append(name);
} catch (IOException e) {
throw new AtlasException(e);
}
return;
}
typesInProcess.add(name);
try {
buf.append(getClass().getSimpleName()).append('{');
buf.append("name=").append(name);
buf.append(", description=").append(description);
buf.append(", superTypes=").append(superTypes.toString());
buf.append(", immediateAttrs=[");
UnmodifiableIterator<AttributeInfo> it = immediateAttrs.iterator();
while (it.hasNext()) {
AttributeInfo attrInfo = it.next();
attrInfo.output(buf, typesInProcess);
if (it.hasNext()) {
buf.append(", ");
}
else {
buf.append(']');
}
}
buf.append("}");
}
catch(IOException e) {
throw new AtlasException(e);
}
finally {
typesInProcess.remove(name);
}
}
public Set<String> getAllSuperTypeNames() {
return superTypePaths.keySet();
}
public Iterator<Path> pathIterator() {
return new PathItr();
}
static class Path {
public final String typeName;
public final String pathName;
public final String pathAfterThis;
private final Path subTypePath;
/*
* name mapping for attributes hidden by a SubType.
*/ Map<String, String> hiddenAttributeMap;
Path(String typeName, Path childPath) throws AtlasException {
this.typeName = typeName;
this.subTypePath = childPath;
if (childPath.contains(typeName)) {
throw new CyclicTypeDefinition(this);
}
pathName = String.format("%s.%s", typeName, childPath.pathName);
pathAfterThis = childPath.pathName;
}
Path(String typeName) {
assert getClass() == Node.class;
this.typeName = typeName;
this.subTypePath = null;
pathName = typeName;
pathAfterThis = null;
}
public boolean contains(String typeName) {
return this.typeName.equals(typeName) || (subTypePath != null && subTypePath.contains(typeName));
}
public String pathString(String nodeSep) {
StringBuilder b = new StringBuilder();
Path p = this;
while (p != null) {
b.append(p.typeName);
p = p.subTypePath;
if (p != null) {
b.append(nodeSep);
}
}
return b.toString();
}
String addOverrideAttr(String name) {
hiddenAttributeMap = hiddenAttributeMap == null ? new HashMap<String, String>() : hiddenAttributeMap;
String oName = pathName + "." + name;
hiddenAttributeMap.put(name, oName);
return oName;
}
}
static class Node extends Path {
Node(String typeName) {
super(typeName);
}
}
static class CyclicTypeDefinition extends AtlasException {
CyclicTypeDefinition(Path p) {
super(String.format("Cycle in Type Definition %s", p.pathString(" -> ")));
}
}
class PathItr implements Iterator<Path> {
Queue<Path> pathQueue;
PathItr() {
pathQueue = new LinkedList<Path>();
pathQueue.add(pathNameToPathMap.get(getName()));
}
@Override
public boolean hasNext() {
return !pathQueue.isEmpty();
}
@Override
public Path next() {
Path p = pathQueue.poll();
ST t = null;
try {
t = (ST) typeSystem.getDataType(superTypeClass, p.typeName);
} catch (AtlasException me) {
throw new RuntimeException(me);
}
if (t.superTypes != null) {
ImmutableSet<String> sTs = t.superTypes;
for (String sT : sTs) {
String nm = sT + "." + p.pathName;
pathQueue.add(pathNameToPathMap.get(nm));
}
}
return p;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
}