blob: c6122647610d6a9a829f61d5cb5f444361e6b890 [file] [log] [blame]
Gamze Abaka1e5ccf52018-07-02 11:59:03 +00001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.opencord.sadis.impl;
17
Elia Battiston1e792b42022-02-17 13:02:55 +010018import com.fasterxml.jackson.databind.DeserializationFeature;
Gamze Abaka1e5ccf52018-07-02 11:59:03 +000019import com.fasterxml.jackson.databind.ObjectMapper;
Elia Battiston1e792b42022-02-17 13:02:55 +010020import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
Gamze Abaka1e5ccf52018-07-02 11:59:03 +000021import com.google.common.cache.Cache;
22import com.google.common.cache.CacheBuilder;
23import com.google.common.collect.Maps;
24import org.slf4j.Logger;
25import org.slf4j.LoggerFactory;
26
27import java.io.IOException;
28import java.io.InputStream;
29import java.net.MalformedURLException;
30import java.net.URL;
31import java.util.Map;
32import java.util.concurrent.TimeUnit;
33
34import org.onosproject.codec.JsonCodec;
35import org.onosproject.core.ApplicationId;
36import org.onosproject.net.config.ConfigFactory;
37import org.onosproject.net.config.NetworkConfigRegistry;
38import org.opencord.sadis.BaseInformation;
39import org.opencord.sadis.BaseConfig;
40import org.opencord.sadis.BaseInformationService;
Matteo Scandoloc3e53722020-12-08 15:14:53 -080041
Gamze Abaka1e5ccf52018-07-02 11:59:03 +000042import java.util.Set;
43
44
45public abstract class InformationAdapter<T extends BaseInformation, K extends BaseConfig<T>>
46 implements BaseInformationService<T> {
47
48 private final Logger log = LoggerFactory.getLogger(this.getClass());
49
50 protected static final int DEFAULT_MAXIMUM_CACHE_SIZE = 0;
51 protected static final long DEFAULT_TTL = 0;
52 protected String url;
53 protected ObjectMapper mapper;
54 protected Cache<String, T> cache;
55 protected int maxiumCacheSize = DEFAULT_MAXIMUM_CACHE_SIZE;
56 protected long cacheEntryTtl = DEFAULT_TTL;
57
58 protected Map<String, T> localCfgData = null;
59
60 public final void updateConfig(NetworkConfigRegistry cfgService) {
61 K cfg = getConfig(cfgService);
62 if (cfg == null) {
63 this.log.warn("Configuration not available");
64 return;
65 }
66 this.log.info("Cache Max Size: {}", cfg.getCacheMaxSize());
67 this.log.info("Cache TTL: {}", cfg.getCacheTtl().getSeconds());
68 this.log.info("Entries: {}", cfg.getEntries());
69
70 configure(cfg);
71 }
72
73 public K getConfig(NetworkConfigRegistry cfgService) {
74 return cfgService.getConfig(getAppId(), getConfigClass());
75 }
76
77 /**
78 * Configures the Adapter for data source and cache parameters.
79 *
80 * @param cfg Configuration data.
81 */
82 public void configure(K cfg) {
83
84 String url = null;
85 try {
Gamze Abaka1e5ccf52018-07-02 11:59:03 +000086 if (cfg.getUrl() != null) {
87 url = cfg.getUrl().toString();
Gamze Abaka1e5ccf52018-07-02 11:59:03 +000088 }
89 } catch (MalformedURLException mUrlEx) {
90 log.error("Invalid URL specified: {}", mUrlEx);
91 }
92
Matteo Scandoloc3e53722020-12-08 15:14:53 -080093 // always load local data
94 localCfgData = Maps.newConcurrentMap();
95 cfg.getEntries().forEach(entry ->
96 localCfgData.put(entry.id(), entry));
97
Gamze Abaka1e5ccf52018-07-02 11:59:03 +000098 int maximumCacheSeize = cfg.getCacheMaxSize();
99 long cacheEntryTtl = cfg.getCacheTtl().getSeconds();
100
101 // Rebuild cache if needed
102 if (isUrlChanged(url) || maximumCacheSeize != this.maxiumCacheSize ||
103 cacheEntryTtl != this.cacheEntryTtl) {
104 this.maxiumCacheSize = maximumCacheSeize;
105 this.cacheEntryTtl = cacheEntryTtl;
106 this.url = url;
107
108 Cache<String, T> newCache = CacheBuilder.newBuilder()
109 .maximumSize(maxiumCacheSize).expireAfterAccess(cacheEntryTtl, TimeUnit.SECONDS).build();
110 Cache<String, T> oldCache = cache;
111
112 synchronized (this) {
113 cache = newCache;
114 }
115
116 oldCache.invalidateAll();
117 oldCache.cleanUp();
118 }
119 }
120
121 protected boolean isUrlChanged(String url) {
122 if (url == null && this.url == null) {
123 return false;
124 }
125 return !((url == this.url) || (url != null && url.equals(this.url)));
126 }
127
128 /*
129 * (non-Javadoc)
130 *
131 * @see
Matteo Scandoloc3e53722020-12-08 15:14:53 -0800132 * org.opencord.sadis.SadisService#clearLocalData()
133 */
134 @Override
135 public void clearLocalData() {
136 localCfgData.clear();
137 }
138
139 /*
140 * (non-Javadoc)
141 *
142 * @see
Gamze Abaka1e5ccf52018-07-02 11:59:03 +0000143 * org.opencord.sadis.SadisService#clearCache()
144 */
145 @Override
146 public void invalidateAll() {
147 cache.invalidateAll();
148 }
149
150 /*
151 * (non-Javadoc)
152 *
153 * @see
154 * org.opencord.sadis.BaseInformationService#invalidateId()
155 */
156 @Override
157 public void invalidateId(String id) {
158 cache.invalidate(id);
159 }
160
161 /*
162 * (non-Javadoc)
163 *
164 * @see
165 * org.opencord.sadis.BaseInformationService#getfromCache(java.lang.
166 * String)
167 */
Andrea Campanella092df202021-11-29 10:38:45 -0800168 //TODO remove
Gamze Abaka1e5ccf52018-07-02 11:59:03 +0000169 @Override
170 public T getfromCache(String id) {
171 Cache<String, T> local;
172 synchronized (this) {
173 local = cache;
174 }
175 T info = local.getIfPresent(id);
176 if (info != null) {
177 return info;
178 }
179 return null;
180 }
181
182 /*
183 * (non-Javadoc)
184 *
185 * @see
186 * org.opencord.sadis.BaseInformationService#get(java.lang.
187 * String)
188 */
189 @Override
190 public T get(String id) {
191 Cache<String, T> local;
192 synchronized (this) {
193 local = cache;
194 }
195
196 T info = local.getIfPresent(id);
197 if (info != null) {
198 return info;
199 }
200
201 /*
Matteo Scandoloc3e53722020-12-08 15:14:53 -0800202 * Not in cache, check for it in the locally configured data,
203 * if it's not there and we have a URL configured
204 * we can attempt to get it from there
Gamze Abaka1e5ccf52018-07-02 11:59:03 +0000205 */
Gamze Abaka1e5ccf52018-07-02 11:59:03 +0000206
Matteo Scandoloc3e53722020-12-08 15:14:53 -0800207 log.debug("Getting data from local config");
208 info = (localCfgData == null) ? null : localCfgData.get(id);
209
210 if (log.isTraceEnabled()) {
211 if (info == null) {
212 log.trace("Data not found in local config.");
213 } else {
214 log.trace("Found data in local config.");
Gamze Abaka1e5ccf52018-07-02 11:59:03 +0000215 }
Matteo Scandoloc3e53722020-12-08 15:14:53 -0800216 }
217
218 if (info == null && this.url != null) {
Gamze Abaka1e5ccf52018-07-02 11:59:03 +0000219 // Augment URL with query parameters
220 String urlWithSubId = this.url.replaceAll("%s", id);
Matteo Scandolofd4d68d2020-10-08 17:37:56 -0700221 log.debug("Getting data from the remote URL {}", urlWithSubId);
Gamze Abaka1e5ccf52018-07-02 11:59:03 +0000222
223 try (InputStream io = new URL(urlWithSubId).openStream()) {
224 info = mapper.readValue(io, getInformationClass());
Elia Battiston1e792b42022-02-17 13:02:55 +0100225 } catch (UnrecognizedPropertyException e) {
226 log.warn("Unknown property in remote json: \"{}\". Will attempt parsing again while ignoring " +
227 "unknown properties", e.getPropertyName());
228 log.debug("Exception while parsing remote json: {}", e.getMessage(), e);
229
230 mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
231
232 try (InputStream io = new URL(urlWithSubId).openStream()) {
233 info = mapper.readValue(io, getInformationClass());
234 } catch (IOException ex) {
235 //Json exceptions extend IOException, so everything will be catched here
236 log.debug("Exception while reading remote data {} ", ex.getMessage(), ex);
237 }
238
239 mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
Gamze Abaka1e5ccf52018-07-02 11:59:03 +0000240 } catch (IOException e) {
241 // TODO use a better http library that allows us to read status code
Andrea Campanella30041b92022-01-28 16:09:08 +0100242 log.debug("Exception while reading remote data {} ", e.getMessage(), e);
Gamze Abaka1e5ccf52018-07-02 11:59:03 +0000243 }
244 }
Matteo Scandoloc3e53722020-12-08 15:14:53 -0800245
246 if (info != null) {
247 local.put(id, info);
yasin sapli2c93ddb2021-06-11 17:46:26 +0300248 log.debug("Returning Info {}", info);
Matteo Scandoloc3e53722020-12-08 15:14:53 -0800249 return info;
250 } else {
251 log.warn("Data not found for id {}", id);
252 return null;
253 }
Gamze Abaka1e5ccf52018-07-02 11:59:03 +0000254 }
255
256 public abstract void registerModule();
257
258 public abstract Set<ConfigFactory> getConfigFactories();
259
260 public abstract JsonCodec<T> getCodec();
261
262 public abstract Class<T> getInformationClass();
263
264 public abstract Class<K> getConfigClass();
265
266 public abstract ApplicationId getAppId();
267}