code: /*
   * 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.skywalking.oap.server.core.analysis;
  
  import com.google.common.base.Charsets;
  import com.google.common.hash.Hashing;
  import java.nio.charset.StandardCharsets;
  import java.util.Base64;
  import lombok.EqualsAndHashCode;
  import lombok.Getter;
  import lombok.RequiredArgsConstructor;
  import org.apache.skywalking.oap.server.core.Const;
  import org.apache.skywalking.oap.server.core.UnexpectedException;
  import org.apache.skywalking.oap.server.library.util.BooleanUtils;
  import org.apache.skywalking.oap.server.library.util.StringUtil;
  
  /**
   * IDManager includes all ID encode/decode functions for service, service instance and endpoint.
   */
  public class IDManager {
      /**
       * Service ID related functions.
       */
      public static class ServiceID {
          /**
           * @param name     service name
           * @param isNormal `true` represents this service is detected by an agent. `false` represents this service is
           *                 conjectured by telemetry data collected from agents on/in the `normal` service.
           */
          public static String buildId(String name, boolean isNormal) {
              if (StringUtil.isBlank(name)) {
                  name = Const.BLANK_ENTITY_NAME;
              }
              return encode(name) + Const.SERVICE_ID_CONNECTOR + BooleanUtils.booleanToValue(isNormal);
          }
  
          /**
           * @return service ID object decoded from {@link #buildId(String, boolean)} result
           */
          public static ServiceIDDefinition analysisId(String id) {
              final String[] strings = id.split(Const.SERVICE_ID_PARSER_SPLIT);
              if (strings.length != 2) {
                  throw new UnexpectedException("Can't split service id into 2 parts, " + id);
              }
              return new ServiceID.ServiceIDDefinition(
                  decode(strings[0]),
                  BooleanUtils.valueToBoolean(Integer.parseInt(strings[1]))
              );
          }
  
          /**
           * @return encoded service relation id
           */
          public static String buildRelationId(ServiceRelationDefine define) {
              return define.sourceId + Const.RELATION_ID_CONNECTOR + define.destId;
          }
  
          /**
           * @return service relation ID object decoded from {@link #buildRelationId(ServiceRelationDefine)} result
           */
          public static ServiceRelationDefine analysisRelationId(String entityId) {
              String[] parts = entityId.split(Const.RELATION_ID_PARSER_SPLIT);
              if (parts.length != 2) {
                  throw new RuntimeException("Illegal Service Relation entity id");
              }
              return new ServiceRelationDefine(parts[0], parts[1]);
          }
  
          @RequiredArgsConstructor
          @Getter
          @EqualsAndHashCode
          public static class ServiceIDDefinition {
              private final String name;
              /**
               * TRUE means an agent installed or directly detected service. FALSE means a conjectural service
               */
              private final boolean isReal;
          }
  
          @RequiredArgsConstructor
          @Getter
          @EqualsAndHashCode
          public static class ServiceRelationDefine {
              private final String sourceId;
              private final String destId;
          }
      }
  
      /**
       * Service Instance ID related functions.
       */
      public static class ServiceInstanceID {
          /**
           * @param serviceId built by {@link ServiceID#buildId(String, boolean)}
           * @return service instance id
           */
          public static String buildId(String serviceId, String instanceName) {
              if (StringUtil.isBlank(instanceName)) {
                  instanceName = Const.BLANK_ENTITY_NAME;
              }
              return serviceId
                  + Const.ID_CONNECTOR
                  + encode(instanceName);
          }
  
          /**
           * @return service instance id object decoded from {@link #buildId(String, String)} result
           */
          public static ServiceInstanceID.InstanceIDDefinition analysisId(String id) {
              final String[] strings = id.split(Const.ID_PARSER_SPLIT);
              if (strings.length != 2) {
                  throw new UnexpectedException("Can't split instance id into 2 parts, " + id);
              }
              return new ServiceInstanceID.InstanceIDDefinition(
                  strings[0],
                  decode(strings[1])
              );
          }
  
          /**
           * @return encoded service instance relation id
           */
          public static String buildRelationId(ServiceInstanceRelationDefine define) {
              return define.sourceId + Const.RELATION_ID_CONNECTOR + define.destId;
          }
  
          /**
           * @return service instance relation ID object decoded from {@link #buildRelationId(ServiceInstanceRelationDefine)}
           * result
           */
          public static ServiceInstanceID.ServiceInstanceRelationDefine analysisRelationId(String entityId) {
              String[] parts = entityId.split(Const.RELATION_ID_PARSER_SPLIT);
              if (parts.length != 2) {
                  throw new RuntimeException("Illegal Service Instance Relation entity id");
              }
              return new ServiceInstanceID.ServiceInstanceRelationDefine(parts[0], parts[1]);
          }
  
          @RequiredArgsConstructor
          @Getter
          public static class InstanceIDDefinition {
              /**
               * Built by {@link ServiceID#buildId(String, boolean)}
               */
              private final String serviceId;
              private final String name;
          }
  
          @RequiredArgsConstructor
          @Getter
          @EqualsAndHashCode
          public static class ServiceInstanceRelationDefine {
              /**
               * Built by {@link ServiceInstanceID#buildId(String, String)}
               */
              private final String sourceId;
              /**
               * Built by {@link ServiceInstanceID#buildId(String, String)}
               */
              private final String destId;
          }
      }
  
      /**
       * Endpoint ID related functions.
       */
      public static class EndpointID {
          /**
           * @param serviceId built by {@link ServiceID#buildId(String, boolean)}
           * @return endpoint id
           */
          public static String buildId(String serviceId, String endpointName) {
              if (StringUtil.isBlank(endpointName)) {
                  endpointName = Const.BLANK_ENTITY_NAME;
              }
              return serviceId
                  + Const.ID_CONNECTOR
                  + encode(endpointName);
          }
  
          /**
           * @return Endpoint id object decoded from {@link #buildId(String, String)} result.
           */
          public static EndpointIDDefinition analysisId(String id) {
              final String[] strings = id.split(Const.ID_PARSER_SPLIT);
              if (strings.length != 2) {
                  throw new UnexpectedException("Can't split endpoint id into 2 parts, " + id);
              }
              return new EndpointIDDefinition(
                  strings[0],
                  decode(strings[1])
              );
          }
  
          /**
           * @return the endpoint relationship string id.
           */
          public static String buildRelationId(EndpointRelationDefine define) {
              return define.sourceServiceId
                  + Const.RELATION_ID_CONNECTOR
                  + encode(define.source)
                  + Const.RELATION_ID_CONNECTOR
                  + define.destServiceId
                  + Const.RELATION_ID_CONNECTOR
                  + encode(define.dest);
          }
  
          /**
           * @return endpoint relation ID object decoded from {@link #buildRelationId(EndpointRelationDefine)} result
           */
          public static EndpointRelationDefine analysisRelationId(String entityId) {
              String[] parts = entityId.split(Const.RELATION_ID_PARSER_SPLIT);
              if (parts.length != 4) {
                  throw new UnexpectedException("Illegal endpoint Relation entity id, " + entityId);
              }
              return new EndpointRelationDefine(
                  parts[0],
                  decode(parts[1]),
                  parts[2],
                  decode(parts[3])
              );
          }
  
          @RequiredArgsConstructor
          @Getter
          public static class EndpointIDDefinition {
              /**
               * Built by {@link ServiceID#buildId(String, boolean)}
               */
              private final String serviceId;
              private final String endpointName;
          }
  
          @RequiredArgsConstructor
          @Getter
          @EqualsAndHashCode
          public static class EndpointRelationDefine {
              /**
               * Built by {@link ServiceID#buildId(String, boolean)}
               */
              private final String sourceServiceId;
              private final String source;
              /**
               * Built by {@link ServiceID#buildId(String, boolean)}
               */
              private final String destServiceId;
              private final String dest;
          }
      }
  
      /**
       * Process ID related functions.
       */
      public static class ProcessID {
          /**
           * @param instanceId built by {@link ServiceInstanceID#buildId(String, String)}
           * @param name       process name
           * @return process id
           */
          public static String buildId(String instanceId, String name) {
              if (StringUtil.isBlank(name)) {
                  name = Const.BLANK_ENTITY_NAME;
              }
              return Hashing.sha256().newHasher().putString(String.format("%s_%s",
                                                                          name, instanceId
              ), Charsets.UTF_8).hash().toString();
          }
  
          /**
           * @return encoded process relation id
           */
          public static String buildRelationId(ProcessRelationDefine define) {
              return define.sourceId + Const.RELATION_ID_CONNECTOR + define.destId;
          }
  
          /**
           * @return process relation ID object decoded from {@link #buildRelationId(ProcessRelationDefine)} result
           */
          public static ProcessRelationDefine analysisRelationId(String entityId) {
              String[] parts = entityId.split(Const.RELATION_ID_PARSER_SPLIT);
              if (parts.length != 2) {
                  throw new RuntimeException("Illegal Process Relation entity id");
              }
              return new ProcessRelationDefine(parts[0], parts[1]);
          }
  
          @RequiredArgsConstructor
          @Getter
          @EqualsAndHashCode
          public static class ProcessRelationDefine {
              private final String sourceId;
              private final String destId;
          }
      }
  
      /**
       * Network Address Alias ID related functions.
       */
      public static class NetworkAddressAliasDefine {
          /**
           * @return encoded network address id
           */
          public static String buildId(String name) {
              return encode(name);
          }
  
          /**
           * @return network address id object decoded from {@link #buildId(String)} result
           */
          public static String analysisId(String id) {
              return decode(id);
          }
      }
  
      /**
       * @param text normal literal string
       * @return Base64 encoded UTF-8 string
       */
      private static String encode(String text) {
          return new String(Base64.getEncoder().encode(text.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
      }
  
      /**
       * @param base64text Base64 encoded UTF-8 string
       * @return normal literal string
       */
      private static String decode(String base64text) {
          return new String(Base64.getDecoder().decode(base64text), StandardCharsets.UTF_8);
      }
  }
  