/** * 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 java.io.IOException; import java.nio.charset.Charset; import java.security.MessageDigest; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.atlas.AtlasConstants; import org.apache.atlas.AtlasException; import org.apache.atlas.typesystem.IStruct; import org.apache.atlas.typesystem.ITypedStruct; public class StructType extends AbstractDataType<IStruct> implements IConstructableType<IStruct, ITypedStruct> { public final TypeSystem typeSystem; public final FieldMapping fieldMapping; public final Map<AttributeInfo, List<String>> infoToNameMap; public final int numFields; private final TypedStructHandler handler; protected StructType(TypeSystem typeSystem, String name, String description, int numFields) { this(typeSystem, name, description, AtlasConstants.DEFAULT_TYPE_VERSION, numFields); } protected StructType(TypeSystem typeSystem, String name, String description, String version, int numFields) { super(name, description, version); this.typeSystem = typeSystem; this.fieldMapping = null; infoToNameMap = null; this.numFields = numFields; this.handler = null; } protected StructType(TypeSystem typeSystem, String name, String description, AttributeInfo... fields) throws AtlasException { this(typeSystem, name, description, AtlasConstants.DEFAULT_TYPE_VERSION, fields); } protected StructType(TypeSystem typeSystem, String name, String description, String version, AttributeInfo... fields) throws AtlasException { super(name, description, version); this.typeSystem = typeSystem; this.fieldMapping = constructFieldMapping(fields); infoToNameMap = TypeUtils.buildAttrInfoToNameMap(this.fieldMapping); this.numFields = this.fieldMapping.fields.size(); this.handler = new TypedStructHandler(this); } public FieldMapping fieldMapping() { return fieldMapping; } /** * 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); StructType newStructType = (StructType) newType; try { TypeUtils.validateUpdate(fieldMapping, newStructType.fieldMapping); } catch (TypeUpdateException e) { throw new TypeUpdateException(newType, e); } } protected FieldMapping constructFieldMapping(AttributeInfo... fields) throws AtlasException { Map<String, AttributeInfo> fieldsMap = new LinkedHashMap<>(); Map<String, Integer> fieldPos = new HashMap<>(); Map<String, Integer> fieldNullPos = 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; for (AttributeInfo i : fields) { if (fieldsMap.containsKey(i.name)) { throw new AtlasException( String.format("Struct defintion cannot contain multiple fields with the same " + "name %s", i.name)); } fieldsMap.put(i.name, i); fieldNullPos.put(i.name, fieldNullPos.size()); if (i.dataType() == DataTypes.BOOLEAN_TYPE) { fieldPos.put(i.name, numBools); numBools++; } else if (i.dataType() == DataTypes.BYTE_TYPE) { fieldPos.put(i.name, numBytes); numBytes++; } else if (i.dataType() == DataTypes.SHORT_TYPE) { fieldPos.put(i.name, numShorts); numShorts++; } else if (i.dataType() == DataTypes.INT_TYPE) { fieldPos.put(i.name, numInts); numInts++; } else if (i.dataType() == DataTypes.LONG_TYPE) { fieldPos.put(i.name, numLongs); numLongs++; } else if (i.dataType() == DataTypes.FLOAT_TYPE) { fieldPos.put(i.name, numFloats); numFloats++; } else if (i.dataType() == DataTypes.DOUBLE_TYPE) { fieldPos.put(i.name, numDoubles); numDoubles++; } else if (i.dataType() == DataTypes.BIGINTEGER_TYPE) { fieldPos.put(i.name, numBigInts); numBigInts++; } else if (i.dataType() == DataTypes.BIGDECIMAL_TYPE) { fieldPos.put(i.name, numBigDecimals); numBigDecimals++; } else if (i.dataType() == DataTypes.DATE_TYPE) { fieldPos.put(i.name, numDates); numDates++; } else if (i.dataType() == DataTypes.STRING_TYPE) { fieldPos.put(i.name, 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(i.name, numArrays); numArrays++; } else if (i.dataType().getTypeCategory() == DataTypes.TypeCategory.MAP) { fieldPos.put(i.name, numMaps); numMaps++; } else if (i.dataType().getTypeCategory() == DataTypes.TypeCategory.STRUCT || i.dataType().getTypeCategory() == DataTypes.TypeCategory.TRAIT) { fieldPos.put(i.name, numStructs); numStructs++; } else if (i.dataType().getTypeCategory() == DataTypes.TypeCategory.CLASS) { fieldPos.put(i.name, numReferenceables); numReferenceables++; } else { throw new AtlasException(String.format("Unknown datatype %s", i.dataType())); } } return new FieldMapping(fieldsMap, fieldPos, fieldNullPos, numBools, numBytes, numShorts, numInts, numLongs, numFloats, numDoubles, numBigInts, numBigDecimals, numDates, numStrings, numArrays, numMaps, numStructs, numReferenceables); } @Override public DataTypes.TypeCategory getTypeCategory() { return DataTypes.TypeCategory.STRUCT; } @Override public ITypedStruct convert(Object val, Multiplicity m) throws AtlasException { return handler.convert(val, m); } public ITypedStruct createInstance() { return handler.createInstance(); } @Override public void output(IStruct s, Appendable buf, String prefix, Set<IStruct> inProcess) throws AtlasException { handler.output(s, buf, prefix, inProcess); } @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()); buf.append("{name=").append(name); buf.append(", description=").append(description); buf.append(", fieldMapping.fields=["); Iterator<AttributeInfo> it = fieldMapping.fields.values().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); } } @Override public void updateSignatureHash(MessageDigest digester, Object val) throws AtlasException { if( !(val instanceof ITypedStruct)) { throw new IllegalArgumentException("Unexpected value type " + val.getClass().getSimpleName() + ". Expected instance of ITypedStruct"); } digester.update(getName().getBytes(Charset.forName("UTF-8"))); if(fieldMapping.fields != null && val != null) { IStruct typedValue = (IStruct) val; for (AttributeInfo aInfo : fieldMapping.fields.values()) { Object attrVal = typedValue.get(aInfo.name); if(attrVal != null) { aInfo.dataType().updateSignatureHash(digester, attrVal); } } } } public List<String> getNames(AttributeInfo info) { return infoToNameMap.get(info); } }