You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@atlas.apache.org by sh...@apache.org on 2016/05/13 09:51:40 UTC

incubator-atlas git commit: ATLAS-645 FieldMapping.output() results in stack overflow when instances reference each other (dkantor via shwethags)

Repository: incubator-atlas
Updated Branches:
  refs/heads/master 454feb47a -> 28991c52b


ATLAS-645 FieldMapping.output() results in stack overflow when instances reference each other (dkantor via shwethags)


Project: http://git-wip-us.apache.org/repos/asf/incubator-atlas/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-atlas/commit/28991c52
Tree: http://git-wip-us.apache.org/repos/asf/incubator-atlas/tree/28991c52
Diff: http://git-wip-us.apache.org/repos/asf/incubator-atlas/diff/28991c52

Branch: refs/heads/master
Commit: 28991c52b3e74278bd49c1d79674ff3f9b49a4f7
Parents: 454feb4
Author: Shwetha GS <ss...@hortonworks.com>
Authored: Fri May 13 15:21:34 2016 +0530
Committer: Shwetha GS <ss...@hortonworks.com>
Committed: Fri May 13 15:21:34 2016 +0530

----------------------------------------------------------------------
 release-log.txt                                 |   1 +
 .../persistence/ReferenceableInstance.java      |   5 +-
 .../typesystem/persistence/StructInstance.java  |  24 +--
 .../typesystem/types/AbstractDataType.java      |  22 ++-
 .../atlas/typesystem/types/AttributeInfo.java   |  36 +++--
 .../atlas/typesystem/types/ClassType.java       |   5 +-
 .../atlas/typesystem/types/DataTypes.java       |   3 +-
 .../atlas/typesystem/types/FieldMapping.java    |  78 +++++++---
 .../typesystem/types/HierarchicalType.java      |  55 ++++++-
 .../atlas/typesystem/types/IDataType.java       |  21 ++-
 .../atlas/typesystem/types/Multiplicity.java    |   3 +-
 .../atlas/typesystem/types/StructType.java      |  63 +++++++-
 .../atlas/typesystem/types/TraitType.java       |   5 +-
 .../typesystem/types/TypedStructHandler.java    |   6 +-
 .../typesystem/types/FieldMappingTest.java      | 151 +++++++++++++++++++
 15 files changed, 408 insertions(+), 70 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/28991c52/release-log.txt
----------------------------------------------------------------------
diff --git a/release-log.txt b/release-log.txt
index baa4c67..9892b0c 100644
--- a/release-log.txt
+++ b/release-log.txt
@@ -20,6 +20,7 @@ ATLAS-409 Atlas will not import avro tables with schema read from a file (dosset
 ATLAS-379 Create sqoop and falcon metadata addons (venkatnrangan,bvellanki,sowmyaramesh via shwethags)
 
 ALL CHANGES:
+ATLAS-645 FieldMapping.output() results in stack overflow when instances reference each other (dkantor via shwethags)
 ATLAS-733 UI: "undefined" XHR request is made for every entity GET page request. (kevalbhatt18 via yhemanth)
 ATLAS-663,ATLAS-673 Install Setup: SOLR (tbeerbower via sumasai)
 ATLAS-629 Kafka messages in ATLAS_HOOK might be lost in HA mode at the instant of failover. (yhemanth)

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/28991c52/typesystem/src/main/java/org/apache/atlas/typesystem/persistence/ReferenceableInstance.java
----------------------------------------------------------------------
diff --git a/typesystem/src/main/java/org/apache/atlas/typesystem/persistence/ReferenceableInstance.java b/typesystem/src/main/java/org/apache/atlas/typesystem/persistence/ReferenceableInstance.java
index 31ef49d..561cb62 100755
--- a/typesystem/src/main/java/org/apache/atlas/typesystem/persistence/ReferenceableInstance.java
+++ b/typesystem/src/main/java/org/apache/atlas/typesystem/persistence/ReferenceableInstance.java
@@ -20,7 +20,9 @@ package org.apache.atlas.typesystem.persistence;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+
 import org.apache.atlas.AtlasException;
+import org.apache.atlas.typesystem.IReferenceableInstance;
 import org.apache.atlas.typesystem.IStruct;
 import org.apache.atlas.typesystem.ITypedReferenceableInstance;
 import org.apache.atlas.typesystem.ITypedStruct;
@@ -33,6 +35,7 @@ import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.security.MessageDigest;
 import java.util.Date;
+import java.util.HashSet;
 
 /*
  * @todo handle names prefixed by traitName.
@@ -89,7 +92,7 @@ public class ReferenceableInstance extends StructInstance implements ITypedRefer
             StringBuilder buf = new StringBuilder();
             String prefix = "";
 
-            fieldMapping.output(this, buf, prefix);
+            fieldMapping.output(this, buf, prefix, new HashSet<IReferenceableInstance>());
             return buf.toString();
 
         } catch (AtlasException me) {

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/28991c52/typesystem/src/main/java/org/apache/atlas/typesystem/persistence/StructInstance.java
----------------------------------------------------------------------
diff --git a/typesystem/src/main/java/org/apache/atlas/typesystem/persistence/StructInstance.java b/typesystem/src/main/java/org/apache/atlas/typesystem/persistence/StructInstance.java
index af62442..6fb2087 100755
--- a/typesystem/src/main/java/org/apache/atlas/typesystem/persistence/StructInstance.java
+++ b/typesystem/src/main/java/org/apache/atlas/typesystem/persistence/StructInstance.java
@@ -20,8 +20,8 @@ package org.apache.atlas.typesystem.persistence;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+
 import org.apache.atlas.AtlasException;
-import org.apache.atlas.typesystem.IStruct;
 import org.apache.atlas.typesystem.ITypedStruct;
 import org.apache.atlas.typesystem.types.AttributeInfo;
 import org.apache.atlas.typesystem.types.ClassType;
@@ -31,7 +31,6 @@ import org.apache.atlas.typesystem.types.EnumValue;
 import org.apache.atlas.typesystem.types.FieldMapping;
 import org.apache.atlas.typesystem.types.StructType;
 import org.apache.atlas.typesystem.types.TypeSystem;
-import org.apache.atlas.typesystem.types.TypeUtils;
 import org.apache.atlas.typesystem.types.ValueConversionException;
 import org.apache.atlas.utils.MD5Utils;
 
@@ -724,32 +723,13 @@ public class StructInstance implements ITypedStruct {
         strings[pos] = val;
     }
 
-    public void output(IStruct s, Appendable buf, String prefix) throws AtlasException {
-        TypeUtils.outputVal("{", buf, prefix);
-        if (s == null) {
-            TypeUtils.outputVal("<null>\n", buf, "");
-            return;
-        }
-        TypeUtils.outputVal("\n", buf, "");
-        String fieldPrefix = prefix + "\t";
-        for (Map.Entry<String, AttributeInfo> e : fieldMapping.fields.entrySet()) {
-            String attrName = e.getKey();
-            AttributeInfo i = e.getValue();
-            Object aVal = s.get(attrName);
-            TypeUtils.outputVal(attrName + " : ", buf, fieldPrefix);
-            i.dataType().output(aVal, buf, "");
-            TypeUtils.outputVal("\n", buf, "");
-        }
-        TypeUtils.outputVal("\n}\n", buf, "");
-    }
-
     @Override
     public String toString() {
         try {
             StringBuilder buf = new StringBuilder();
             String prefix = "";
 
-            fieldMapping.output(this, buf, prefix);
+            fieldMapping.output(this, buf, prefix, null);
             return buf.toString();
 
         } catch (AtlasException me) {

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/28991c52/typesystem/src/main/java/org/apache/atlas/typesystem/types/AbstractDataType.java
----------------------------------------------------------------------
diff --git a/typesystem/src/main/java/org/apache/atlas/typesystem/types/AbstractDataType.java b/typesystem/src/main/java/org/apache/atlas/typesystem/types/AbstractDataType.java
index 92be3c7..dc9cdf2 100755
--- a/typesystem/src/main/java/org/apache/atlas/typesystem/types/AbstractDataType.java
+++ b/typesystem/src/main/java/org/apache/atlas/typesystem/types/AbstractDataType.java
@@ -22,7 +22,9 @@ import com.google.common.collect.ImmutableSortedMap;
 
 import org.apache.atlas.AtlasException;
 
+import java.io.IOException;
 import java.util.Map;
+import java.util.Set;
 
 abstract class AbstractDataType<T> implements IDataType<T> {
 
@@ -44,7 +46,7 @@ abstract class AbstractDataType<T> implements IDataType<T> {
     }
 
     @Override
-    public void output(T val, Appendable buf, String prefix) throws AtlasException {
+    public void output(T val, Appendable buf, String prefix, Set<T> inProcess) throws AtlasException {
         if (val instanceof Map) {
             ImmutableSortedMap immutableSortedMap = ImmutableSortedMap.copyOf((Map) val);
             TypeUtils.outputVal(val == null ? "<null>" : immutableSortedMap.toString(), buf, prefix);
@@ -53,6 +55,24 @@ abstract class AbstractDataType<T> implements IDataType<T> {
         }
     }
 
+    @Override
+    public void output(Appendable buf, Set<String> typesInProcess) throws AtlasException {
+
+        try {
+            buf.append(toString());
+        } catch (IOException e) {
+            throw new AtlasException(e);
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return "{name=" + name + ", description=" + description + "}";
+    }
+
     /**
      * Validate that current definition can be updated with the new definition
      * @param newType

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/28991c52/typesystem/src/main/java/org/apache/atlas/typesystem/types/AttributeInfo.java
----------------------------------------------------------------------
diff --git a/typesystem/src/main/java/org/apache/atlas/typesystem/types/AttributeInfo.java b/typesystem/src/main/java/org/apache/atlas/typesystem/types/AttributeInfo.java
index e748579..9cb0d0d 100755
--- a/typesystem/src/main/java/org/apache/atlas/typesystem/types/AttributeInfo.java
+++ b/typesystem/src/main/java/org/apache/atlas/typesystem/types/AttributeInfo.java
@@ -22,7 +22,10 @@ import org.apache.atlas.AtlasException;
 import org.codehaus.jettison.json.JSONException;
 import org.codehaus.jettison.json.JSONObject;
 
+import java.io.IOException;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
 public class AttributeInfo {
     public final String name;
@@ -60,15 +63,30 @@ public class AttributeInfo {
 
     @Override
     public String toString() {
-        return "AttributeInfo{" +
-                "name='" + name + '\'' +
-                ", dataType=" + dataType +
-                ", multiplicity=" + multiplicity +
-                ", isComposite=" + isComposite +
-                ", isUnique=" + isUnique +
-                ", isIndexable=" + isIndexable +
-                ", reverseAttributeName='" + reverseAttributeName + '\'' +
-                '}';
+        StringBuilder buf = new StringBuilder();
+        try {
+            output(buf, new HashSet<String>());
+        } catch (AtlasException e) {
+            throw new RuntimeException(e);
+        }
+        return buf.toString();
+    }
+
+    public void output(Appendable buf, Set<String> typesInProcess) throws AtlasException {
+        try {
+            buf.append("{name=").append(name);
+            buf.append(", dataType=");
+            dataType.output(buf, typesInProcess);
+            buf.append(", multiplicity=").append(multiplicity.toString());
+            buf.append(", isComposite=").append(Boolean.toString(isComposite));
+            buf.append(", isUnique=").append(Boolean.toString(isUnique));
+            buf.append(", isIndexable=").append(Boolean.toString(isIndexable));
+            buf.append(", reverseAttributeName=").append(reverseAttributeName);
+            buf.append('}');
+        }
+        catch(IOException e) {
+            throw new AtlasException(e);
+        }
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/28991c52/typesystem/src/main/java/org/apache/atlas/typesystem/types/ClassType.java
----------------------------------------------------------------------
diff --git a/typesystem/src/main/java/org/apache/atlas/typesystem/types/ClassType.java b/typesystem/src/main/java/org/apache/atlas/typesystem/types/ClassType.java
index 90cf3cc..c56987a 100755
--- a/typesystem/src/main/java/org/apache/atlas/typesystem/types/ClassType.java
+++ b/typesystem/src/main/java/org/apache/atlas/typesystem/types/ClassType.java
@@ -41,6 +41,7 @@ import java.security.MessageDigest;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 public class ClassType extends HierarchicalType<ClassType, IReferenceableInstance>
         implements IConstructableType<IReferenceableInstance, ITypedReferenceableInstance> {
@@ -207,8 +208,8 @@ public class ClassType extends HierarchicalType<ClassType, IReferenceableInstanc
     }
 
     @Override
-    public void output(IReferenceableInstance s, Appendable buf, String prefix) throws AtlasException {
-        fieldMapping.output(s, buf, prefix);
+    public void output(IReferenceableInstance s, Appendable buf, String prefix, Set<IReferenceableInstance> inProcess) throws AtlasException {
+        fieldMapping.output(s, buf, prefix, inProcess);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/28991c52/typesystem/src/main/java/org/apache/atlas/typesystem/types/DataTypes.java
----------------------------------------------------------------------
diff --git a/typesystem/src/main/java/org/apache/atlas/typesystem/types/DataTypes.java b/typesystem/src/main/java/org/apache/atlas/typesystem/types/DataTypes.java
index 55ec91f..41b3427 100755
--- a/typesystem/src/main/java/org/apache/atlas/typesystem/types/DataTypes.java
+++ b/typesystem/src/main/java/org/apache/atlas/typesystem/types/DataTypes.java
@@ -38,6 +38,7 @@ import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 public class DataTypes {
 
@@ -425,7 +426,7 @@ public class DataTypes {
         }
 
         @Override
-        public void output(Date val, Appendable buf, String prefix) throws AtlasException {
+        public void output(Date val, Appendable buf, String prefix, Set<Date> inProcess) throws AtlasException {
             TypeUtils.outputVal(val == null ? "<null>" : TypeSystem.getInstance().getDateFormat().format(val), buf,
                     prefix);
         }

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/28991c52/typesystem/src/main/java/org/apache/atlas/typesystem/types/FieldMapping.java
----------------------------------------------------------------------
diff --git a/typesystem/src/main/java/org/apache/atlas/typesystem/types/FieldMapping.java b/typesystem/src/main/java/org/apache/atlas/typesystem/types/FieldMapping.java
index 36149ba..a2b3db2 100755
--- a/typesystem/src/main/java/org/apache/atlas/typesystem/types/FieldMapping.java
+++ b/typesystem/src/main/java/org/apache/atlas/typesystem/types/FieldMapping.java
@@ -23,7 +23,9 @@ import org.apache.atlas.typesystem.IReferenceableInstance;
 import org.apache.atlas.typesystem.IStruct;
 import org.apache.atlas.typesystem.persistence.Id;
 
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
 public class FieldMapping {
 
@@ -70,7 +72,7 @@ public class FieldMapping {
         this.numReferenceables = numReferenceables;
     }
 
-    protected void outputFields(IStruct s, Appendable buf, String fieldPrefix) throws AtlasException {
+    protected void outputFields(IStruct s, Appendable buf, String fieldPrefix, Set<? extends IStruct> inProcess) throws AtlasException {
         for (Map.Entry<String, AttributeInfo> e : fields.entrySet()) {
             String attrName = e.getKey();
             AttributeInfo i = e.getValue();
@@ -79,52 +81,82 @@ public class FieldMapping {
             if (aVal != null && aVal instanceof Id) {
                 TypeUtils.outputVal(aVal.toString(), buf, "");
             } else {
-                i.dataType().output(aVal, buf, fieldPrefix);
+                i.dataType().output(aVal, buf, fieldPrefix, inProcess);
             }
             TypeUtils.outputVal("\n", buf, "");
         }
     }
 
-    public void output(IStruct s, Appendable buf, String prefix) throws AtlasException {
+    public void output(IStruct s, Appendable buf, String prefix, Set<IStruct> inProcess) throws AtlasException {
         if (s == null) {
             TypeUtils.outputVal("<null>\n", buf, "");
             return;
         }
-        TypeUtils.outputVal("{", buf, prefix);
 
-        TypeUtils.outputVal("\n", buf, "");
-        String fieldPrefix = prefix + "\t";
+        if (inProcess == null) {
+            inProcess = new HashSet<>();
+        }
+        else if (inProcess.contains(s)) {
+            // Avoid infinite recursion when structs reference each other.
+            return;
+        }
+        inProcess.add(s);
+
+        try {
+            TypeUtils.outputVal("{", buf, prefix);
+
+            TypeUtils.outputVal("\n", buf, "");
+            String fieldPrefix = prefix + "\t";
 
-        outputFields(s, buf, fieldPrefix);
+            outputFields(s, buf, fieldPrefix, inProcess);
 
-        TypeUtils.outputVal("}", buf, prefix);
+            TypeUtils.outputVal("}", buf, prefix);
+        }
+        finally {
+            inProcess.remove(s);
+        }
     }
 
-    public void output(IReferenceableInstance s, Appendable buf, String prefix) throws AtlasException {
+    public void output(IReferenceableInstance s, Appendable buf, String prefix, Set<IReferenceableInstance> inProcess) throws AtlasException {
         if (s == null) {
             TypeUtils.outputVal("<null>\n", buf, "");
             return;
         }
-        TypeUtils.outputVal("{", buf, prefix);
 
-        TypeUtils.outputVal("\n", buf, "");
-        String fieldPrefix = prefix + "\t";
+        if (inProcess == null) {
+            inProcess = new HashSet<>();
+        }
+        else if (inProcess.contains(s)) {
+            // Avoid infinite recursion when structs reference each other.
+            return;
+        }
+        inProcess.add(s);
 
-        TypeUtils.outputVal("id : ", buf, fieldPrefix);
-        TypeUtils.outputVal(s.getId().toString(), buf, "");
-        TypeUtils.outputVal("\n", buf, "");
+        try {
+            TypeUtils.outputVal("{", buf, prefix);
 
-        outputFields(s, buf, fieldPrefix);
+            TypeUtils.outputVal("\n", buf, "");
+            String fieldPrefix = prefix + "\t";
 
-        TypeSystem ts = TypeSystem.getInstance();
+            TypeUtils.outputVal("id : ", buf, fieldPrefix);
+            TypeUtils.outputVal(s.getId().toString(), buf, "");
+            TypeUtils.outputVal("\n", buf, "");
 
-        for (String sT : s.getTraits()) {
-            TraitType tt = ts.getDataType(TraitType.class, sT);
-            TypeUtils.outputVal(sT + " : ", buf, fieldPrefix);
-            tt.output(s.getTrait(sT), buf, fieldPrefix);
-        }
+            outputFields(s, buf, fieldPrefix, inProcess);
+
+            TypeSystem ts = TypeSystem.getInstance();
 
-        TypeUtils.outputVal("}", buf, prefix);
+            for (String sT : s.getTraits()) {
+                TraitType tt = ts.getDataType(TraitType.class, sT);
+                TypeUtils.outputVal(sT + " : ", buf, fieldPrefix);
+                tt.output(s.getTrait(sT), buf, fieldPrefix, null);
+            }
+
+            TypeUtils.outputVal("}", buf, prefix);
+        }
+        finally {
+            inProcess.remove(s);
+        }
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/28991c52/typesystem/src/main/java/org/apache/atlas/typesystem/types/HierarchicalType.java
----------------------------------------------------------------------
diff --git a/typesystem/src/main/java/org/apache/atlas/typesystem/types/HierarchicalType.java b/typesystem/src/main/java/org/apache/atlas/typesystem/types/HierarchicalType.java
index 89fcea6..859ec72 100755
--- a/typesystem/src/main/java/org/apache/atlas/typesystem/types/HierarchicalType.java
+++ b/typesystem/src/main/java/org/apache/atlas/typesystem/types/HierarchicalType.java
@@ -21,12 +21,14 @@ 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;
@@ -367,9 +369,58 @@ public abstract class HierarchicalType<ST extends HierarchicalType, T> extends A
 
     @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;
+        }
 
-        return "[name=" + name + ", description=" + description + 
-            ", superTypes=" + superTypes + ", immediateAttrs=" + immediateAttrs + "]";
+        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() {

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/28991c52/typesystem/src/main/java/org/apache/atlas/typesystem/types/IDataType.java
----------------------------------------------------------------------
diff --git a/typesystem/src/main/java/org/apache/atlas/typesystem/types/IDataType.java b/typesystem/src/main/java/org/apache/atlas/typesystem/types/IDataType.java
index 373ad2c..85ddee7 100755
--- a/typesystem/src/main/java/org/apache/atlas/typesystem/types/IDataType.java
+++ b/typesystem/src/main/java/org/apache/atlas/typesystem/types/IDataType.java
@@ -21,6 +21,7 @@ package org.apache.atlas.typesystem.types;
 import org.apache.atlas.AtlasException;
 
 import java.security.MessageDigest;
+import java.util.Set;
 
 public interface IDataType<T> {
     String getName();
@@ -29,7 +30,25 @@ public interface IDataType<T> {
 
     DataTypes.TypeCategory getTypeCategory();
 
-    void output(T val, Appendable buf, String prefix) throws AtlasException;
+    /**
+     * Output a string representation of a value instance of this type.
+     *
+     * @param val
+     * @param buf
+     * @param prefix
+     * @param inProcess
+     * @throws AtlasException
+     */
+    void output(T val, Appendable buf, String prefix, Set<T> inProcess) throws AtlasException;
+
+    /**
+     * Output a string representation of this type.
+     *
+     * @param buf
+     * @param typesInProcess
+     * @throws AtlasException
+     */
+    void output(Appendable buf, Set<String> typesInProcess) throws AtlasException;
 
     void validateUpdate(IDataType newType) throws TypeUpdateException;
 

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/28991c52/typesystem/src/main/java/org/apache/atlas/typesystem/types/Multiplicity.java
----------------------------------------------------------------------
diff --git a/typesystem/src/main/java/org/apache/atlas/typesystem/types/Multiplicity.java b/typesystem/src/main/java/org/apache/atlas/typesystem/types/Multiplicity.java
index a54dabc..06da32e 100755
--- a/typesystem/src/main/java/org/apache/atlas/typesystem/types/Multiplicity.java
+++ b/typesystem/src/main/java/org/apache/atlas/typesystem/types/Multiplicity.java
@@ -79,8 +79,7 @@ public final class Multiplicity {
 
     @Override
     public String toString() {
-        return "Multiplicity{" +
-                "lower=" + lower +
+        return "{lower=" + lower +
                 ", upper=" + upper +
                 ", isUnique=" + isUnique +
                 '}';

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/28991c52/typesystem/src/main/java/org/apache/atlas/typesystem/types/StructType.java
----------------------------------------------------------------------
diff --git a/typesystem/src/main/java/org/apache/atlas/typesystem/types/StructType.java b/typesystem/src/main/java/org/apache/atlas/typesystem/types/StructType.java
index 54e344f..6f40c1d 100755
--- a/typesystem/src/main/java/org/apache/atlas/typesystem/types/StructType.java
+++ b/typesystem/src/main/java/org/apache/atlas/typesystem/types/StructType.java
@@ -18,12 +18,16 @@
 
 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.AtlasException;
 import org.apache.atlas.typesystem.IStruct;
@@ -182,8 +186,63 @@ public class StructType extends AbstractDataType<IStruct> implements IConstructa
     }
 
     @Override
-    public void output(IStruct s, Appendable buf, String prefix) throws AtlasException {
-        handler.output(s, buf, prefix);
+    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

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/28991c52/typesystem/src/main/java/org/apache/atlas/typesystem/types/TraitType.java
----------------------------------------------------------------------
diff --git a/typesystem/src/main/java/org/apache/atlas/typesystem/types/TraitType.java b/typesystem/src/main/java/org/apache/atlas/typesystem/types/TraitType.java
index 84c22bf..f23bf5b 100755
--- a/typesystem/src/main/java/org/apache/atlas/typesystem/types/TraitType.java
+++ b/typesystem/src/main/java/org/apache/atlas/typesystem/types/TraitType.java
@@ -28,6 +28,7 @@ import java.nio.charset.Charset;
 import java.security.MessageDigest;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 public class TraitType extends HierarchicalType<TraitType, IStruct>
         implements IConstructableType<IStruct, ITypedStruct> {
@@ -63,8 +64,8 @@ public class TraitType extends HierarchicalType<TraitType, IStruct>
     }
 
     @Override
-    public void output(IStruct s, Appendable buf, String prefix) throws AtlasException {
-        handler.output(s, buf, prefix);
+    public void output(IStruct s, Appendable buf, String prefix, Set<IStruct> inProcess) throws AtlasException {
+        handler.output(s, buf, prefix, inProcess);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/28991c52/typesystem/src/main/java/org/apache/atlas/typesystem/types/TypedStructHandler.java
----------------------------------------------------------------------
diff --git a/typesystem/src/main/java/org/apache/atlas/typesystem/types/TypedStructHandler.java b/typesystem/src/main/java/org/apache/atlas/typesystem/types/TypedStructHandler.java
index da246d6..b97669a 100755
--- a/typesystem/src/main/java/org/apache/atlas/typesystem/types/TypedStructHandler.java
+++ b/typesystem/src/main/java/org/apache/atlas/typesystem/types/TypedStructHandler.java
@@ -20,6 +20,7 @@ package org.apache.atlas.typesystem.types;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+
 import org.apache.atlas.AtlasException;
 import org.apache.atlas.typesystem.IStruct;
 import org.apache.atlas.typesystem.ITypedStruct;
@@ -32,6 +33,7 @@ import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.util.Date;
 import java.util.Map;
+import java.util.Set;
 
 public class TypedStructHandler {
 
@@ -104,8 +106,8 @@ public class TypedStructHandler {
                 fieldMapping.numReferenceables == 0 ? null : new Id[fieldMapping.numReferenceables]);
     }
 
-    public void output(IStruct s, Appendable buf, String prefix) throws AtlasException {
-        fieldMapping.output(s, buf, prefix);
+    public void output(IStruct s, Appendable buf, String prefix, Set<IStruct> inProcess) throws AtlasException {
+        fieldMapping.output(s, buf, prefix, inProcess);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/28991c52/typesystem/src/test/java/org/apache/atlas/typesystem/types/FieldMappingTest.java
----------------------------------------------------------------------
diff --git a/typesystem/src/test/java/org/apache/atlas/typesystem/types/FieldMappingTest.java b/typesystem/src/test/java/org/apache/atlas/typesystem/types/FieldMappingTest.java
new file mode 100644
index 0000000..0259ade
--- /dev/null
+++ b/typesystem/src/test/java/org/apache/atlas/typesystem/types/FieldMappingTest.java
@@ -0,0 +1,151 @@
+/**
+ * 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.util.HashSet;
+
+import org.apache.atlas.typesystem.IReferenceableInstance;
+import org.apache.atlas.typesystem.ITypedReferenceableInstance;
+import org.apache.atlas.typesystem.ITypedStruct;
+import org.apache.atlas.typesystem.TypesDef;
+import org.apache.atlas.typesystem.types.AttributeDefinition;
+import org.apache.atlas.typesystem.types.ClassType;
+import org.apache.atlas.typesystem.types.EnumTypeDefinition;
+import org.apache.atlas.typesystem.types.HierarchicalTypeDefinition;
+import org.apache.atlas.typesystem.types.Multiplicity;
+import org.apache.atlas.typesystem.types.StructTypeDefinition;
+import org.apache.atlas.typesystem.types.TraitType;
+import org.apache.atlas.typesystem.types.TypeSystem;
+import org.apache.atlas.typesystem.types.utils.TypesUtil;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+
+/**
+ * Unit test for {@link FieldMapping}
+ *
+ */
+public class FieldMappingTest {
+
+    @BeforeTest
+    public void beforeTest() throws Exception {
+        TypeSystem typeSystem = TypeSystem.getInstance();
+        typeSystem.reset();
+    }
+
+    @Test
+    public void testOutputReferenceableInstance() throws Exception {
+        // ATLAS-645: verify that FieldMapping.output(IReferenceableInstance)
+        // does not infinitely recurse when ITypedReferenceableInstance's reference each other.
+        HierarchicalTypeDefinition<ClassType> valueDef = TypesUtil.createClassTypeDef("Value",
+            ImmutableSet.<String>of(),
+            new AttributeDefinition("owner", "Owner", Multiplicity.OPTIONAL, false, null));
+
+        // Define class type with reference, where the value is a class reference to Value.
+        HierarchicalTypeDefinition<ClassType> ownerDef = TypesUtil.createClassTypeDef("Owner",
+            ImmutableSet.<String>of(),
+            new AttributeDefinition("value", "Value", Multiplicity.OPTIONAL, false, null));
+        TypesDef typesDef = TypesUtil.getTypesDef(ImmutableList.<EnumTypeDefinition>of(),
+            ImmutableList.<StructTypeDefinition>of(), ImmutableList.<HierarchicalTypeDefinition<TraitType>>of(),
+            ImmutableList.of(ownerDef, valueDef));
+
+        TypeSystem typeSystem = TypeSystem.getInstance();
+        typeSystem.defineTypes(typesDef);
+        ClassType ownerType = typeSystem.getDataType(ClassType.class, "Owner");
+
+        // Prior to fix for ATLAS-645, this call would throw a StackOverflowError
+        try {
+            ownerType.toString();
+        }
+        catch (StackOverflowError e) {
+            Assert.fail("Infinite recursion in ClassType.toString() caused StackOverflowError");
+        }
+
+        ClassType valueType = typeSystem.getDataType(ClassType.class, "Value");
+
+        // Create instances of Owner and Value that reference each other.
+        ITypedReferenceableInstance ownerInstance = ownerType.createInstance();
+        ITypedReferenceableInstance valueInstance = valueType.createInstance();
+        // Set Owner.value reference to Value instance.
+        ownerInstance.set("value", valueInstance);
+        // Set Value.owner reference on Owner instance.
+        valueInstance.set("owner", ownerInstance);
+
+        // Prior to fix for ATLAS-645, this call would throw a StackOverflowError
+        try {
+            ownerInstance.fieldMapping().output(ownerInstance, new StringBuilder(), "", new HashSet<IReferenceableInstance>());
+        }
+        catch (StackOverflowError e) {
+            Assert.fail("Infinite recursion in FieldMapping.output() caused StackOverflowError");
+        }
+    }
+
+    @Test
+    public void testOutputStruct() throws Exception {
+        // ATLAS-645: verify that FieldMapping.output(IStruct) does not infinitely recurse
+        // when an IStruct and ITypedReferenceableInstance reference each other.
+        HierarchicalTypeDefinition<ClassType> valueDef = TypesUtil.createClassTypeDef("Value",
+            ImmutableSet.<String>of(),
+            new AttributeDefinition("owner", "Owner", Multiplicity.OPTIONAL, false, null));
+
+
+        // Define struct type with reference, where the value is a class reference to Value.
+        StructTypeDefinition ownerDef = TypesUtil.createStructTypeDef("Owner",
+             new AttributeDefinition("value", "Value", Multiplicity.OPTIONAL, false, null));
+
+        TypesDef typesDef = TypesUtil.getTypesDef(ImmutableList.<EnumTypeDefinition>of(),
+            ImmutableList.of(ownerDef), ImmutableList.<HierarchicalTypeDefinition<TraitType>>of(),
+            ImmutableList.of(valueDef));
+
+        TypeSystem typeSystem = TypeSystem.getInstance();
+        typeSystem.reset();
+        typeSystem.defineTypes(typesDef);
+        StructType ownerType = typeSystem.getDataType(StructType.class, "Owner");
+        ClassType valueType = typeSystem.getDataType(ClassType.class, "Value");
+
+        // Prior to fix for ATLAS-645, this call would throw a StackOverflowError
+        try {
+            ownerType.toString();
+        }
+        catch (StackOverflowError e) {
+            Assert.fail("Infinite recursion in StructType.toString() caused StackOverflowError");
+        }
+
+
+        // Create instances of Owner and Value that reference each other.
+        ITypedStruct ownerInstance = ownerType.createInstance();
+        ITypedReferenceableInstance valueInstance = valueType.createInstance();
+        // Set Owner.value reference to Value instance.
+        ownerInstance.set("value", valueInstance);
+        // Set Value.owner reference on Owner instance.
+        valueInstance.set("owner", ownerInstance);
+
+        // Prior to fix for ATLAS-645, this call would throw a StackOverflowError
+        try {
+            ownerInstance.fieldMapping().output(ownerInstance, new StringBuilder(), "", null);
+        }
+        catch (StackOverflowError e) {
+            Assert.fail("Infinite recursion in FieldMapping.output() caused StackOverflowError");
+        }
+
+    }
+}