2 * ========================LICENSE_START=================================
5 * Copyright (C) 2020 AT&T Intellectual Property
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ========================LICENSE_END===================================
20 package org.oransc.ric.portal.dashboard;
23 import java.io.IOException;
24 import java.lang.invoke.MethodHandles;
25 import java.nio.file.Files;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.ListIterator;
30 import javax.servlet.http.HttpServletResponse;
32 import org.onap.portalsdk.core.onboarding.exception.PortalAPIException;
33 import org.onap.portalsdk.core.restful.domain.EcompUser;
34 import org.oransc.ric.portal.dashboard.model.StatsDetailsTransport;
35 import org.oransc.ric.portal.dashboard.exception.StatsManagerException;
36 import org.oransc.ric.portal.dashboard.model.IDashboardResponse;
37 import org.oransc.ric.portal.dashboard.model.AppStats;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40 import org.springframework.stereotype.Component;
41 import org.springframework.stereotype.Service;
43 import com.fasterxml.jackson.core.type.TypeReference;
44 import com.fasterxml.jackson.databind.ObjectMapper;
47 * Provides simple xApp stats-management services.
49 * This first implementation serializes xApp stat details to a file.
51 * Migrate to a database someday?
54 public class AppStatsManager {
56 private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
58 // This default value is only useful for development and testing.
59 public static final String STATS_FILE_PATH = "app-stats.json";
61 private final File statsFile;
62 private final List<AppStats> stats;
63 private int appMaxId = -1;
66 * Development/test-only constructor that uses default file path.
68 * @param clear If true, start empty and remove any existing file.
70 * @throws IOException On file error
72 public AppStatsManager(boolean clear) throws IOException {
73 this(STATS_FILE_PATH);
75 logger.debug("ctor: removing file {}", statsFile.getAbsolutePath());
76 File f = new File(AppStatsManager.STATS_FILE_PATH);
78 Files.delete(f.toPath());
84 * Constructor that accepts a file path
86 * @param statsFilePath File path
87 * @throws IOException If file cannot be read
89 public AppStatsManager(final String statsFilePath) throws IOException {
90 logger.debug("ctor: statsfile {}", statsFilePath);
91 if (statsFilePath == null)
92 throw new IllegalArgumentException("Missing or empty stats file property");
93 statsFile = new File(statsFilePath);
94 logger.debug("ctor: managing stats in file {}", statsFile.getAbsolutePath());
95 if (statsFile.exists()) {
96 final ObjectMapper mapper = new ObjectMapper();
97 stats = mapper.readValue(statsFile, new TypeReference<List<AppStats>>() {
99 for (AppStats st: stats) {
100 if (st.getStatsDetails().getAppId()>appMaxId)
101 appMaxId = st.getStatsDetails().getAppId();
104 stats = new ArrayList<>();
109 * Gets the current app metric stats.
111 * @return List of App stat objects, possibly empty
113 public List<AppStats> getStats() {
118 * Gets the current app metric stats by instance key.
120 * @param instanceKey Desired instance key
121 * @return List of App stat objects by instance key, possibly empty
123 public List<AppStats> getStatsByInstance(String instanceKey) {
124 List<AppStats> statsByInstance = new ArrayList<AppStats>();
125 for (AppStats st : this.stats) {
126 if (st.getInstanceKey().equals(instanceKey)) {
127 logger.debug("getStatsByInstance: match on instance key {}", instanceKey);
128 statsByInstance.add(st);
131 return statsByInstance;
135 * Gets the stats with the specified app Id and instance key
137 * @param appId Desired app Id
138 * @param instanceKey Desired instance key
139 * @return Stats object; null if Id is not known
141 public AppStats getStatsById(String instanceKey, int appId) {
143 for (AppStats st : this.stats) {
144 if (st.getInstanceKey().equals(instanceKey) && st.getStatsDetails().getAppId() == appId) {
145 logger.debug("getStatsById: match on app id {} with instance key {}", appId, instanceKey);
149 logger.debug("getStatsById: no match on app id with instance key {}{}", appId, instanceKey);
154 private void saveStats() throws IOException {
155 final ObjectMapper mapper = new ObjectMapper();
156 mapper.writeValue(statsFile, stats);
160 * Allow at most one thread to create a stats at one time.
161 * Before creating new stat, checks for composite key (appname,url) uniqueness for an instance key
163 public synchronized AppStats createStats(String instanceKey, StatsDetailsTransport statsSetupRequest)
164 throws StatsManagerException, IOException {
165 logger.debug("createStats: appId {}, instanceKey {}", statsSetupRequest.getAppId(), instanceKey);
167 for (AppStats st : stats) {
168 if (st.getInstanceKey().equals(instanceKey)
169 && st.getStatsDetails().getAppName().equals(statsSetupRequest.getAppName())
170 && st.getStatsDetails().getMetricUrl().equals(statsSetupRequest.getMetricUrl())) {
171 String msg = "App exists with name " + statsSetupRequest.getAppName() + " and url "+statsSetupRequest.getMetricUrl()+ " on instance key " + instanceKey;
173 throw new StatsManagerException(msg);
177 AppStats newAppStat = null;
178 //Assigns appId to be 1 more than the largest value stored in memory
179 appMaxId = appMaxId+1;
180 newAppStat = new AppStats(instanceKey,
181 new StatsDetailsTransport(appMaxId, statsSetupRequest.getAppName(), statsSetupRequest.getMetricUrl()));
182 stats.add(newAppStat);
188 * Allow at most one thread to modify a stats at one time. We still have
189 * last-edit-wins of course.
191 public synchronized void updateStats(String instanceKey, StatsDetailsTransport statsSetupRequest)
192 throws StatsManagerException, IOException {
193 logger.debug("updateStats: appId {}, instanceKey {}", statsSetupRequest.getAppId(), instanceKey);
194 boolean editStatsObjectFound = false;
196 for (AppStats st : stats) {
197 if (st.getInstanceKey().equals(instanceKey)
198 && st.getStatsDetails().getAppId() == statsSetupRequest.getAppId()) {
199 AppStats newAppStat = new AppStats(instanceKey, statsSetupRequest);
201 stats.add(newAppStat);
202 editStatsObjectFound = true;
207 if (!editStatsObjectFound) {
208 String msg = "Stats to be updated does not exist ";
210 throw new StatsManagerException(msg);
214 public synchronized AppStats deleteStats(String instanceKey, int appId) throws StatsManagerException, IOException {
215 logger.debug("deleteStats: appId {}, instanceKey {}", appId, instanceKey);
216 boolean deleteStatsObjectFound = false;
217 AppStats stat = null;
218 for (AppStats st : stats) {
219 if (st.getInstanceKey().equals(instanceKey) && st.getStatsDetails().getAppId() == appId) {
222 deleteStatsObjectFound = true;
226 } catch (Exception e) {
227 throw new StatsManagerException(e.toString());
232 if (!deleteStatsObjectFound) {
233 String msg = "deleteStats: no match on app id {} of instance key {}";
234 logger.warn(msg, appId, instanceKey);
235 throw new StatsManagerException(msg);