Repository: pinpoint-apm/pinpoint
Issue: Handle cases where services have the same application name with different service type.

Backporting fixes to 1.1.0.

Issue resolves the following cases.
1. Services with the same application name but different service types should be selectable separately - #214 
2. User nodes now take the destination's service type along with the application name when drawing the server map - #254 


Changed files:
web/src/main/java/com/navercorp/pinpoint/web/dao/hbase/HbaseApplicationIndexDao.java
web/src/main/java/com/navercorp/pinpoint/web/mapper/MapStatisticsCalleeMapper.java
commons/src/main/java/com/navercorp/pinpoint/common/util/ApplicationMapStatisticsUtils.java
web/src/main/java/com/navercorp/pinpoint/web/mapper/ApplicationNameMapper.java

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 a0ff208e00..948e912ba1 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
@@ -103,6 +103,12 @@ public class ApplicationMapStatisticsUtils {
         return BytesUtils.toStringAndRightTrim(bytes, 6, length);
     }
 
+    public static String getDestApplicationNameFromColumnNameForUser(byte[] bytes, ServiceType destServiceType) {
+        String destApplicationName = getDestApplicationNameFromColumnName(bytes);
+        String destServiceTypeName = destServiceType.name();
+        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/dao/hbase/HbaseApplicationIndexDao.java b/web/src/main/java/com/navercorp/pinpoint/web/dao/hbase/HbaseApplicationIndexDao.java
index f4ef005500..9b5d1cc420 100644
--- a/web/src/main/java/com/navercorp/pinpoint/web/dao/hbase/HbaseApplicationIndexDao.java
+++ b/web/src/main/java/com/navercorp/pinpoint/web/dao/hbase/HbaseApplicationIndexDao.java
@@ -16,6 +16,7 @@
 
 package com.navercorp.pinpoint.web.dao.hbase;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import org.apache.commons.lang.StringUtils;
@@ -45,7 +46,7 @@ public class HbaseApplicationIndexDao implements ApplicationIndexDao {
 
     @Autowired
     @Qualifier("applicationNameMapper")
-    private RowMapper<Application> applicationNameMapper;
+    private RowMapper<List<Application>> applicationNameMapper;
 
     @Autowired
     @Qualifier("agentIdMapper")
@@ -55,7 +56,12 @@ public class HbaseApplicationIndexDao implements ApplicationIndexDao {
     public List<Application> selectAllApplicationNames() {
         Scan scan = new Scan();
         scan.setCaching(30);
-        return hbaseOperations2.find(HBaseTables.APPLICATION_INDEX, scan, applicationNameMapper);
+        List<List<Application>> results = hbaseOperations2.find(HBaseTables.APPLICATION_INDEX, scan, applicationNameMapper);
+        List<Application> applications = new ArrayList<Application>();
+        for (List<Application> result : results) {
+            applications.addAll(result);
+        }
+        return applications;
     }
 
     @Override
diff --git a/web/src/main/java/com/navercorp/pinpoint/web/mapper/ApplicationNameMapper.java b/web/src/main/java/com/navercorp/pinpoint/web/mapper/ApplicationNameMapper.java
index 186e061e0c..62220fc0f3 100644
--- a/web/src/main/java/com/navercorp/pinpoint/web/mapper/ApplicationNameMapper.java
+++ b/web/src/main/java/com/navercorp/pinpoint/web/mapper/ApplicationNameMapper.java
@@ -16,25 +16,50 @@
 
 package com.navercorp.pinpoint.web.mapper;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.hadoop.hbase.Cell;
+import org.apache.hadoop.hbase.CellUtil;
 import org.apache.hadoop.hbase.client.Result;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.springframework.data.hadoop.hbase.RowMapper;
 import org.springframework.stereotype.Component;
 
+import com.navercorp.pinpoint.common.ServiceType;
 import com.navercorp.pinpoint.web.vo.Application;
 
 /**
  *
  */
 @Component
-public class ApplicationNameMapper implements RowMapper<Application> {
+public class ApplicationNameMapper implements RowMapper<List<Application>> {
+    
     @Override
-    public Application mapRow(Result result, int rowNum) throws Exception {
+    public List<Application> mapRow(Result result, int rowNum) throws Exception {
         if (result.isEmpty()) {
-            return null;
+            return Collections.emptyList();
         }
+        Set<Short> uniqueTypeCodes = new HashSet<Short>();
         String applicationName = Bytes.toString(result.getRow());
-        short serviceType = Bytes.toShort(result.value());
-        return new Application(applicationName, serviceType);
+        
+        List<Cell> list = result.listCells();
+        if (list == null) {
+            return Collections.emptyList();
+        }
+        
+        for (Cell cell : list) {
+            short serviceTypeCode = Bytes.toShort(CellUtil.cloneValue(cell));
+            uniqueTypeCodes.add(serviceTypeCode);
+        }
+        List<Application> applications = new ArrayList<Application>();
+        for (short serviceTypeCode : uniqueTypeCodes) {
+            applications.add(new Application(applicationName, ServiceType.findServiceType(serviceTypeCode)));
+        }      
+
+        return applications;
     }
 }
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 2040310f47..6afdf3812b 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
@@ -16,6 +16,7 @@
 
 package com.navercorp.pinpoint.web.mapper;
 
+import com.navercorp.pinpoint.common.ServiceType;
 import com.navercorp.pinpoint.common.buffer.Buffer;
 import com.navercorp.pinpoint.common.buffer.FixedBuffer;
 import com.navercorp.pinpoint.common.util.ApplicationMapStatisticsUtils;
@@ -71,7 +72,7 @@ public class MapStatisticsCalleeMapper implements RowMapper<LinkDataMap> {
         for (Cell cell : result.rawCells()) {
 
             final byte[] qualifier = CellUtil.cloneQualifier(cell);
-            final Application callerApplication = readCallerApplication(qualifier);
+            final Application callerApplication = readCallerApplication(qualifier, calleeApplication.getServiceType());
             if (filter.filter(callerApplication)) {
                 continue;
             }
@@ -98,9 +99,15 @@ public class MapStatisticsCalleeMapper implements RowMapper<LinkDataMap> {
         return linkDataMap;
     }
 
-    private Application readCallerApplication(byte[] qualifier) {
-        String callerApplicationName = ApplicationMapStatisticsUtils.getDestApplicationNameFromColumnName(qualifier);
-        short callerServiceType = ApplicationMapStatisticsUtils.getDestServiceTypeFromColumnName(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 (ServiceType.findServiceType(callerServiceType).isUser()) {
+            callerApplicationName = ApplicationMapStatisticsUtils.getDestApplicationNameFromColumnNameForUser(qualifier, calleeServiceType);
+        } else {
+            callerApplicationName = ApplicationMapStatisticsUtils.getDestApplicationNameFromColumnName(qualifier);
+        }
         return new Application(callerApplicationName, callerServiceType);
     }