Repository: pinpoint-apm/pinpoint
Issue: User node pointing to a node with same application name but different service type throws an exception
Labels: bug
enhancement

In relation to issue #214, applications with the same application name can now be selected separately as long as their service types differ.
However, user nodes are generated on the server map taking only the destination's application name into account. 

For example, if there are 2 nodes with the same application name but different service type (ie TOMCAT, SPRING-BOOT both named myApp) with 2 USER nodes each pointing to myApp[TOMCAT], and myApp[SPRING-BOOT], the Web UI throws an error when drawing the server map. 
This is because the USER node is generated by extracting the application name of the destination node, which in this case would result in only a single USER node being generated.
![current](https://cloud.githubusercontent.com/assets/10057488/6817488/6a663dea-d2e4-11e4-8097-b5c3c622f259.png)

2 possible solutions exist.
1. Draw only a single USER node that points to both myApp[TOMCAT] and myApp[SPRING-BOOT].
   ![solution1](https://cloud.githubusercontent.com/assets/10057488/6817489/75cb29d4-d2e4-11e4-8d75-3f725fda8725.png)
2. Draw 2 separate USER nodes each pointing to myApp[TOMCAT] and myApp[SPRING-BOOT].
   ![solution2](https://cloud.githubusercontent.com/assets/10057488/6817493/7e3dbfc8-d2e4-11e4-8bdf-94025abef7c5.png)

The second solution seems like the better option - but this requires the destination node's service type to already be stored in hbase.


Issue comments:

Pull request review comments:

Patch that resolved the issue:
diff --git a/commons/src/main/java/com/navercorp/pinpoint/common/util/ApplicationMapStatisticsUtils.java b/commons/src/main/java/com/navercorp/pinpoint/common/util/ApplicationMapStatisticsUtils.java
index 4e5c6433e595..d519c794c27d 100644
--- a/commons/src/main/java/com/navercorp/pinpoint/common/util/ApplicationMapStatisticsUtils.java
+++ b/commons/src/main/java/com/navercorp/pinpoint/common/util/ApplicationMapStatisticsUtils.java
@@ -105,7 +105,13 @@ public static String getDestApplicationNameFromColumnName(byte[] bytes) {
         final short length = BytesUtils.bytesToShort(bytes, 4);
         return BytesUtils.toStringAndRightTrim(bytes, 6, length);
     }
-
+    
+    public static String getDestApplicationNameFromColumnNameForUser(byte[] bytes, ServiceType destServiceType) {
+        String destApplicationName = getDestApplicationNameFromColumnName(bytes);
+        String destServiceTypeName = destServiceType.getName();
+        return destApplicationName + "_" + destServiceTypeName;
+    }
+    
     public static String getHost(byte[] bytes) {
         int offset = 6 + BytesUtils.bytesToShort(bytes, 4);
 
diff --git a/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/LinkList.java b/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/LinkList.java
index 60acd9b93577..d34388190ed9 100644
--- a/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/LinkList.java
+++ b/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/LinkList.java
@@ -56,7 +56,7 @@ public List<Link> findToLink(Application toApplication) {
         for (Link link : linkMap.values()) {
             Node toNode = link.getTo();
             // find all the callers of toApplication/destination
-            if (toNode.getApplication().equals(toApplication)) {
+            if (toNode.getApplication().equals(toApplication) && toNode.getServiceType().equals(toApplication.getServiceType())) {
                 findList.add(link);
             }
         }
@@ -70,14 +70,14 @@ public List<Link> findToLink(Application toApplication) {
      */
     public List<Link> findFromLink(Application fromApplication) {
         if (fromApplication == null) {
-            throw new NullPointerException("toApplication must not be null");
+            throw new NullPointerException("fromApplication must not be null");
         }
 
         List<Link> findList = new ArrayList<Link>();
         for (Link link : linkMap.values()) {
             Node fromNode = link.getFrom();
 
-            if (fromNode.getApplication().equals(fromApplication)) {
+            if (fromNode.getApplication().equals(fromApplication) && fromNode.getServiceType().equals(fromApplication.getServiceType())) {
                 findList.add(link);
             }
         }
diff --git a/web/src/main/java/com/navercorp/pinpoint/web/mapper/MapStatisticsCalleeMapper.java b/web/src/main/java/com/navercorp/pinpoint/web/mapper/MapStatisticsCalleeMapper.java
index cdacbb4706bb..ce930cc19762 100644
--- a/web/src/main/java/com/navercorp/pinpoint/web/mapper/MapStatisticsCalleeMapper.java
+++ b/web/src/main/java/com/navercorp/pinpoint/web/mapper/MapStatisticsCalleeMapper.java
@@ -76,7 +76,7 @@ public LinkDataMap mapRow(Result result, int rowNum) throws Exception {
         for (KeyValue kv : result.raw()) {
 
             final byte[] qualifier = kv.getQualifier();
-            final Application callerApplication = readCallerApplication(qualifier);
+            final Application callerApplication = readCallerApplication(qualifier, calleeApplication.getServiceType());
             if (filter.filter(callerApplication)) {
                 continue;
             }
@@ -103,9 +103,16 @@ public LinkDataMap mapRow(Result result, int rowNum) throws Exception {
         return linkDataMap;
     }
 
-    private Application readCallerApplication(byte[] qualifier) {
-        String callerApplicationName = ApplicationMapStatisticsUtils.getDestApplicationNameFromColumnName(qualifier);
+    private Application readCallerApplication(byte[] qualifier, ServiceType calleeServiceType) {
         short callerServiceType = ApplicationMapStatisticsUtils.getDestServiceTypeFromColumnName(qualifier);
+        // Caller may be a user node, and user nodes may call nodes with the same application name but different service type.
+        // To distinguish between these user nodes, append callee's service type to the application name.
+        String callerApplicationName;
+        if (registry.findServiceType(callerServiceType).isUser()) {
+            callerApplicationName = ApplicationMapStatisticsUtils.getDestApplicationNameFromColumnNameForUser(qualifier, calleeServiceType);
+        } else {
+            callerApplicationName = ApplicationMapStatisticsUtils.getDestApplicationNameFromColumnName(qualifier);
+        }
         return createApplication(callerApplicationName, callerServiceType);
     }
 


Test patch: