/*
 * 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.query;

import java.io.IOException;
import java.text.ParseException;
import java.util.*;
import org.apache.skywalking.apm.util.StringUtil;
import org.apache.skywalking.oap.server.core.Const;
import org.apache.skywalking.oap.server.core.analysis.Downsampling;
import org.apache.skywalking.oap.server.core.analysis.metrics.Metrics;
import org.apache.skywalking.oap.server.core.query.entity.*;
import org.apache.skywalking.oap.server.core.query.sql.*;
import org.apache.skywalking.oap.server.core.storage.StorageModule;
import org.apache.skywalking.oap.server.core.storage.annotation.ValueColumnIds;
import org.apache.skywalking.oap.server.core.storage.query.IMetricsQueryDAO;
import org.apache.skywalking.oap.server.library.module.Service;
import org.apache.skywalking.oap.server.library.module.*;
import org.apache.skywalking.oap.server.library.util.CollectionUtils;

/**
 * @author peng-yongsheng
 */
public class MetricQueryService implements Service {

    private final ModuleManager moduleManager;
    private IMetricsQueryDAO metricQueryDAO;

    public MetricQueryService(ModuleManager moduleManager) {
        this.moduleManager = moduleManager;
    }

    private IMetricsQueryDAO getMetricQueryDAO() {
        if (metricQueryDAO == null) {
            metricQueryDAO = moduleManager.find(StorageModule.NAME).provider().getService(IMetricsQueryDAO.class);
        }
        return metricQueryDAO;
    }

    public IntValues getValues(final String indName, final List<String> ids, final Downsampling downsampling, final long startTB,
        final long endTB) throws IOException {
        if (CollectionUtils.isEmpty(ids)) {
            throw new RuntimeException("IDs can't be null");
        }

        Where where = new Where();
        KeyValues intKeyValues = new KeyValues();
        intKeyValues.setKey(Metrics.ENTITY_ID);
        where.getKeyValues().add(intKeyValues);
        ids.forEach(intKeyValues.getValues()::add);

        return getMetricQueryDAO().getValues(indName, downsampling, startTB, endTB, where, ValueColumnIds.INSTANCE.getValueCName(indName), ValueColumnIds.INSTANCE.getValueFunction(indName));
    }

    public IntValues getLinearIntValues(final String indName, final String id, final Downsampling downsampling, final long startTB,
        final long endTB) throws IOException, ParseException {
        List<DurationPoint> durationPoints = DurationUtils.INSTANCE.getDurationPoints(downsampling, startTB, endTB);
        List<String> ids = new ArrayList<>();
        if (StringUtil.isEmpty(id)) {
            durationPoints.forEach(durationPoint -> ids.add(String.valueOf(durationPoint.getPoint())));
        } else {
            durationPoints.forEach(durationPoint -> ids.add(durationPoint.getPoint() + Const.ID_SPLIT + id));
        }

        return getMetricQueryDAO().getLinearIntValues(indName, downsampling, ids, ValueColumnIds.INSTANCE.getValueCName(indName));
    }

    public Thermodynamic getThermodynamic(final String indName, final String id, final Downsampling downsampling, final long startTB,
        final long endTB) throws IOException, ParseException {
        List<DurationPoint> durationPoints = DurationUtils.INSTANCE.getDurationPoints(downsampling, startTB, endTB);
        List<String> ids = new ArrayList<>();
        durationPoints.forEach(durationPoint -> {
            if (id == null) {
                ids.add(String.valueOf(durationPoint.getPoint()));
            } else {
                ids.add(durationPoint.getPoint() + Const.ID_SPLIT + id);
            }
        });

        return getMetricQueryDAO().getThermodynamic(indName, downsampling, ids, ValueColumnIds.INSTANCE.getValueCName(indName));
    }
}

@@ -931,17 +931,48 @@
 va.util.
-*
+ArrayList;%0Aimport java.util.List
 ;%0Aimport
@@ -1264,17 +1264,164 @@
 .entity.
-*
+IntValues;%0Aimport org.apache.skywalking.oap.server.core.query.entity.Thermodynamic;%0Aimport org.apache.skywalking.oap.server.core.query.sql.KeyValues
 ;%0Aimport
@@ -1469,17 +1469,21 @@
 ery.sql.
-*
+Where
 ;%0Aimport
@@ -1701,16 +1701,86 @@
 eryDAO;%0A
+import org.apache.skywalking.oap.server.library.module.ModuleManager;%0A
 import o
@@ -1839,66 +1839,8 @@
 ce;%0A
-import org.apache.skywalking.oap.server.library.module.*;%0A
 impo
@@ -1904,16 +1904,73 @@
 onUtils;
+%0Aimport org.slf4j.Logger;%0Aimport org.slf4j.LoggerFactory;
 %0A%0A/**%0A *
@@ -2051,16 +2051,109 @@
 vice %7B%0A%0A
+    private static final Logger logger = LoggerFactory.getLogger(MetricQueryService.class);%0A%0A
     priv
@@ -2702,32 +2702,40 @@
 ng downsampling,
+%0A       
  final long star
@@ -2846,54 +2846,375 @@
     
-throw new RuntimeException(%22IDs can't be null%22
+/**%0A             * Don't support query values w/o ID. but UI still did this(as bug),%0A             * we return an empty list, and a debug level log,%0A             * rather than an exception, which always being considered as a serious error from new users.%0A             */%0A            logger.debug(%22query metrics%5B%7B%7D%5D w/o IDs%22, indName);%0A            return new IntValues(
 );%0A 
@@ -3758,32 +3758,40 @@
 ng downsampling,
+%0A       
  final long star
@@ -4552,16 +4552,24 @@
 ampling,
+%0A       
  final l

Remove "IDs can't be null" in backend, as UI hasn't fixed this for a long time. (#3039)