Skip to content

Commit 968820e

Browse files
rlakdehirenkp2000cyberhead-pl
authored
CASL-592 Extension Cleanup Capability (#374)
* Initial Changes. Signed-off-by: lakde <rohit.lakde@here.com> * Fix Unit Tests Signed-off-by: lakde <rohit.lakde@here.com> * Review Fixes Signed-off-by: lakde <rohit.lakde@here.com> * Update openapi.yaml * Update gradle.properties * Update gradle.properties * Rebase CASL-321 (#371) * Fixed array patching (#365) * Fixed array patching * corrected patcher test expectation * Fix thread stuck issue + addtnl logs (#368) * Added logs to troubleshoot thread hanging issue * CASL-258 unreleased lock fix (#358) * Fixed array patching (#365) (#367) * corrected patcher test expectation * updated version * updated changelog --------- Co-authored-by: Pawel Mazurek <52866094+cyberhead-pl@users.noreply.github.com> --------- Co-authored-by: Hiren Patel <80465571+hirenkp2000@users.noreply.github.com> Co-authored-by: Pawel Mazurek <52866094+cyberhead-pl@users.noreply.github.com> * Update reusable-build-and-publish.yml * Update gradle.properties * Update openapi.yaml * Add Interface IExtensionInit * Add instanceCache logic. * Create ValueTuple.java * loaderCache Changes * Correct logger messages * Update IExtensionInit description * Fix bug * Correct logger in Hub. * Update IExtensionInit Description. * Correct removeExtensionFromCache. * Review Fixes for ExtensionCache * Update ExtensionCache.java * Update NakshaHub.java * Extension Sub Environment Changes (#330) (#392) * Initial Changes. * Fix Unit Tests * Review Fixes * Rebase CASL-321 (#371) * Fixed array patching (#365) * Fix thread stuck issue + addtnl logs (#368) * CASL-258 unreleased lock fix (#358) * updated version * updated changelog * Update reusable-build-and-publish.yml * Update gradle.properties * Update openapi.yaml --------- Signed-off-by: lakde <rohit.lakde@here.com> * Update ExtensionCache.java * Update NakshaHub.java * Update gradle.properties * Update openapi.yaml * Update NakshaVersion.java * Update gradle.properties * Update gradle.properties * Update NakshaVersion.java * Update NakshaHub.java * Support multiple JWT public keys (#395) (#396) * Updated doc about existing JWT loading * added support for additional pub key * updated readme * Updated version to 2.2.1 * Updated doc for pvt and pub key * Added constants in NakshaHubConfig constructor * addressed review comments Co-authored-by: Hiren Patel <80465571+hirenkp2000@users.noreply.github.com> * Update ExtensionCache.java * Update gradle.properties * Update openapi.yaml * Update NakshaVersion.java * Update logger as per review comment. --------- Signed-off-by: lakde <rohit.lakde@here.com> Co-authored-by: Hiren Patel <80465571+hirenkp2000@users.noreply.github.com> Co-authored-by: Pawel Mazurek <52866094+cyberhead-pl@users.noreply.github.com>
1 parent 3f0a00d commit 968820e

File tree

6 files changed

+154
-34
lines changed

6 files changed

+154
-34
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ mavenPassword=YourPassword
1010
# When updating the version, please as well consider:
1111
# - here-naksha-lib-core/src/main/com/here/naksha/lib/core/NakshaVersion (static property: latest)
1212
# - here-naksha-app-service/src/main/resources/swagger/openapi.yaml (info.version property)
13-
version=2.2.1
13+
version=2.2.2
1414

here-naksha-app-service/src/main/resources/swagger/openapi.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ servers:
1212
info:
1313
title: "Naksha Hub-API"
1414
description: "Naksha Hub-API is a REST API to provide simple access to geo data."
15-
version: "2.2.1"
15+
version: "2.2.2"
1616

1717
security:
1818
- AccessToken: [ ]
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright (C) 2017-2024 HERE Europe B.V.
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+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
* License-Filename: LICENSE
18+
*/
19+
package com.here.naksha.lib.core;
20+
21+
import com.here.naksha.lib.core.models.features.Extension;
22+
23+
/**
24+
* Naksha Extension Interface for all extensions providing initClassName.
25+
*/
26+
public interface IExtensionInit {
27+
28+
/**
29+
* Initializes the extension with the specified hub and extension parameters.
30+
* This method should be called to set up any necessary resources or configurations
31+
* required by the extension to operate correctly.
32+
* @param hub The hub instance to be used by the extension.
33+
* @param extension Extension configuration supplied as part of deployment pipeline for respective Extension and sub-env.
34+
*/
35+
void init(INaksha hub, Extension extension);
36+
37+
/**
38+
* Closes the extension. This method should be called to ensure proper
39+
* cleanup when the extension is no longer needed.
40+
*/
41+
void close();
42+
}

here-naksha-lib-core/src/main/java/com/here/naksha/lib/core/NakshaVersion.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,13 @@ public class NakshaVersion implements Comparable<NakshaVersion> {
5959
public static final String v2_1_1 = "2.1.1";
6060
public static final String v2_2_0 = "2.2.0";
6161
public static final String v2_2_1 = "2.2.1";
62+
public static final String v2_2_2 = "2.2.2";
6263

6364
/**
6465
* The latest version of the naksha-extension stored in the resources.
6566
*/
6667
@AvailableSince(v2_0_5)
67-
public static final NakshaVersion latest = of(v2_2_1);
68+
public static final NakshaVersion latest = of(v2_2_2);
6869

6970
private final int major;
7071
private final int minor;

here-naksha-lib-ext-manager/src/main/java/com/here/naksha/lib/extmanager/ExtensionCache.java

Lines changed: 62 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package com.here.naksha.lib.extmanager;
2020

21+
import com.here.naksha.lib.core.IExtensionInit;
2122
import com.here.naksha.lib.core.INaksha;
2223
import com.here.naksha.lib.core.SimpleTask;
2324
import com.here.naksha.lib.core.models.ExtensionConfig;
@@ -29,7 +30,6 @@
2930
import com.here.naksha.lib.extmanager.models.KVPair;
3031
import java.io.File;
3132
import java.io.IOException;
32-
import java.lang.reflect.InvocationTargetException;
3333
import java.util.HashMap;
3434
import java.util.List;
3535
import java.util.Map;
@@ -46,10 +46,10 @@
4646
*/
4747
public class ExtensionCache {
4848
private static final @NotNull Logger logger = LoggerFactory.getLogger(ExtensionCache.class);
49-
private static final ConcurrentHashMap<String, KVPair<Extension, ClassLoader>> loaderCache =
50-
new ConcurrentHashMap<>();
49+
private static final ConcurrentHashMap<String, ValueTuple> loaderCache = new ConcurrentHashMap<>();
5150
private static final Map<String, FileClient> jarClientMap = new HashMap<>();
5251
private final @NotNull INaksha naksha;
52+
private static final String WHITE_LIST_CLASSES = "whitelistClasses";
5353

5454
static {
5555
jarClientMap.put(JarClientType.S3.getType(), new AmazonS3Helper());
@@ -90,10 +90,8 @@ protected void buildExtensionCache(ExtensionConfig extensionConfig) {
9090

9191
for (String key : loaderCache.keySet()) {
9292
if (!extIds.contains(key)) {
93-
loaderCache.remove(key);
94-
PluginCache.removeExtensionCache(key);
95-
logger.info("Extension {} removed from cache.", key);
96-
}
93+
removeExtensionFromCache(key);
94+
}
9795
}
9896
logger.info("Extension cache size " + loaderCache.size());
9997
}
@@ -103,23 +101,36 @@ private void publishIntoCache(KVPair<Extension, File> result, ExtensionConfig ex
103101
final Extension extension = result.getKey();
104102
final String extensionIdWthEnv = extension.getEnv() + ":" + extension.getId();
105103
final File jarFile = result.getValue();
104+
IExtensionInit initObj = null;
106105
ClassLoader loader;
107106
try {
108-
loader = ClassLoaderHelper.getClassLoader(jarFile, extensionConfig.getWhilelistDelegateClass());
107+
@SuppressWarnings("unchecked")
108+
List<String> whitelistClasses = (List<String>) extension
109+
.getProperties()
110+
.getOrDefault(WHITE_LIST_CLASSES, extensionConfig.getWhilelistDelegateClass());
111+
logger.info("Whitelist classes in use for extension {} are {}", extensionIdWthEnv, whitelistClasses);
112+
loader = ClassLoaderHelper.getClassLoader(jarFile, whitelistClasses);
109113
} catch (Exception e) {
110-
logger.error("Failed to load extension jar " + extension.getId(), e);
114+
logger.error("Failed to load extension jar " + extensionIdWthEnv, e);
111115
return;
112116
}
113117

114118
if (!isNullOrEmpty(extension.getInitClassName())) {
115119
try {
116120
Class<?> clz = loader.loadClass(extension.getInitClassName());
117-
clz.getConstructor(INaksha.class, Extension.class).newInstance(naksha, extension);
118-
} catch (ClassNotFoundException
119-
| InvocationTargetException
120-
| InstantiationException
121-
| NoSuchMethodException
122-
| IllegalAccessException e) {
121+
Object obj = clz.getConstructor().newInstance();
122+
if (obj instanceof IExtensionInit initInstance) {
123+
initInstance.init(naksha, extension);
124+
initObj = initInstance;
125+
logger.info(
126+
"Extension {} initialization using initClassName {} done successfully.",
127+
extensionIdWthEnv,
128+
extension.getInitClassName());
129+
} else {
130+
logger.error("InitClassName {} does not implement IExtensionInit for Extension {}", extension.getInitClassName(), extensionIdWthEnv);
131+
return;
132+
}
133+
} catch (Exception e) {
123134
logger.error(
124135
"Failed to instantiate class {} for extension {} ",
125136
extension.getInitClassName(),
@@ -128,28 +139,48 @@ private void publishIntoCache(KVPair<Extension, File> result, ExtensionConfig ex
128139
return;
129140
}
130141
}
131-
if (!isNullOrEmpty(extension.getInitClassName()))
132-
logger.info(
133-
"Extension {} initialization using initClassName {} done successfully.",
134-
extensionIdWthEnv,
135-
extension.getInitClassName());
136-
loaderCache.put(extensionIdWthEnv, new KVPair<Extension, ClassLoader>(extension, loader));
137-
PluginCache.removeExtensionCache(extensionIdWthEnv);
142+
143+
ValueTuple previousValue = loaderCache.put(extensionIdWthEnv, new ValueTuple(extension, loader, initObj));
144+
if (previousValue != null) {
145+
IExtensionInit previousInitObj = previousValue.getInstance();
146+
closeExtensionInstance(extensionIdWthEnv, previousInitObj);
147+
}
148+
138149
logger.info(
139-
"Extension id={}, version={} is successfully loaded into the cache, using Jar at {} for env={}.",
150+
"Extension id={}, version={} is successfully loaded into the cache, using Jar at {}.",
140151
extensionIdWthEnv,
141152
extension.getVersion(),
142-
extension.getUrl().substring(extension.getUrl().lastIndexOf("/") + 1),
143-
extension.getEnv());
153+
extension.getUrl().substring(extension.getUrl().lastIndexOf("/") + 1));
154+
}
155+
}
156+
157+
private void removeExtensionFromCache(String extensionId) {
158+
ValueTuple valueTuple = loaderCache.remove(extensionId);
159+
PluginCache.removeExtensionCache(extensionId);
160+
logger.info("Extension {} removed from cache.", extensionId);
161+
if (valueTuple != null) {
162+
IExtensionInit initObj = valueTuple.getInstance();
163+
closeExtensionInstance(extensionId, initObj);
164+
}
165+
}
166+
167+
private void closeExtensionInstance(String extensionId, IExtensionInit initObj) {
168+
if (initObj != null) {
169+
try {
170+
initObj.close();
171+
logger.info("Extension {} closed successfully.", extensionId);
172+
} catch (Exception e) {
173+
logger.error("Failed to close extension {}", extensionId, e);
174+
}
144175
}
145176
}
146177

147178
private boolean isLoaderMappingExist(Extension extension) {
148179
final String extensionIdWthEnv = extension.getEnv() + ":" + extension.getId();
149-
KVPair<Extension, ClassLoader> existingMapping = loaderCache.get(extensionIdWthEnv);
180+
ValueTuple existingMapping = loaderCache.get(extensionIdWthEnv);
150181
if (existingMapping == null) return false;
151182

152-
final Extension exExtension = existingMapping.getKey();
183+
final Extension exExtension = existingMapping.getExtension();
153184
final String exInitClassName =
154185
isNullOrEmpty(exExtension.getInitClassName()) ? "" : exExtension.getInitClassName();
155186
final String initClassName = isNullOrEmpty(extension.getInitClassName()) ? "" : extension.getInitClassName();
@@ -160,7 +191,7 @@ private boolean isLoaderMappingExist(Extension extension) {
160191
}
161192

162193
/**
163-
* Lamda function which will initiate the downloading for extension jar
194+
* Lambda function which will initiate the downloading for extension jar
164195
*/
165196
private KVPair<Extension, File> downloadJar(Extension extension) {
166197
logger.info("Downloading jar {} with version {} ", extension.getId(), extension.getVersion());
@@ -184,16 +215,16 @@ protected FileClient getJarClient(String url) {
184215
}
185216

186217
protected ClassLoader getClassLoaderById(@NotNull String extensionId) {
187-
KVPair<Extension, ClassLoader> mappedLoader = loaderCache.get(extensionId);
188-
return mappedLoader == null ? null : mappedLoader.getValue();
218+
ValueTuple mappedLoader = loaderCache.get(extensionId);
219+
return mappedLoader == null ? null : mappedLoader.getClassLoader();
189220
}
190221

191222
public int getCacheLength() {
192223
return loaderCache.size();
193224
}
194225

195226
public List<Extension> getCachedExtensions() {
196-
return loaderCache.values().stream().map(KVPair::getKey).toList();
227+
return loaderCache.values().stream().map(ValueTuple::getExtension).toList();
197228
}
198229

199230
private boolean isNullOrEmpty(String value) {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright (C) 2017-2024 HERE Europe B.V.
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+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
* License-Filename: LICENSE
18+
*/
19+
package com.here.naksha.lib.extmanager;
20+
21+
import com.here.naksha.lib.core.IExtensionInit;
22+
import com.here.naksha.lib.core.models.features.Extension;
23+
24+
public class ValueTuple {
25+
private final Extension extension;
26+
private final ClassLoader classLoader;
27+
private final IExtensionInit instance;
28+
29+
public ValueTuple(Extension extension, ClassLoader classLoader, IExtensionInit instance) {
30+
this.extension = extension;
31+
this.classLoader = classLoader;
32+
this.instance = instance;
33+
}
34+
35+
public Extension getExtension() {
36+
return extension;
37+
}
38+
39+
public ClassLoader getClassLoader() {
40+
return classLoader;
41+
}
42+
43+
public IExtensionInit getInstance() {
44+
return instance;
45+
}
46+
}

0 commit comments

Comments
 (0)