-
Notifications
You must be signed in to change notification settings - Fork 214
/
DittoExtensionPoint.java
201 lines (171 loc) · 8.09 KB
/
DittoExtensionPoint.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/*
* Copyright (c) 2022 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.ditto.internal.utils.extension;
import java.text.MessageFormat;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Nullable;
import org.eclipse.ditto.internal.utils.pekko.PekkoClassLoader;
import org.eclipse.ditto.internal.utils.config.DittoConfigError;
import org.eclipse.ditto.internal.utils.config.ScopedConfig;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigValue;
import com.typesafe.config.ConfigValueType;
import org.apache.pekko.actor.AbstractExtensionId;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.ExtendedActorSystem;
import org.apache.pekko.actor.Extension;
/**
* Extension Point for extending Ditto via Pekko Extensions to provide custom functionality to different
* aspects of the service.
*
* @since 3.0.0
*/
public interface DittoExtensionPoint extends Extension {
/**
* @param <T> the class of the extension for which an implementation should be loaded.
*/
abstract class ExtensionId<T extends org.apache.pekko.actor.Extension> extends AbstractExtensionId<T> {
private final ExtensionIdConfig<T> extensionIdConfig;
/**
* Returns the {@code ExtensionId} for the implementation that should be loaded.
*
* @param extensionIdConfig configuration for the extension ID.
*/
protected ExtensionId(final ExtensionIdConfig<T> extensionIdConfig) {
this.extensionIdConfig = extensionIdConfig;
}
@Override
public T createExtension(final ExtendedActorSystem system) {
return PekkoClassLoader.instantiate(system, extensionIdConfig.parentClass(),
getImplementation(system),
List.of(ActorSystem.class, Config.class),
List.of(system, extensionIdConfig.extensionConfig()));
}
protected ExtensionIdConfig<T> globalConfig(final ActorSystem actorSystem) {
return ExtensionIdConfig.of(
extensionIdConfig.parentClass(),
actorSystem.settings().config(),
getConfigPath()
);
}
protected String getImplementation(final ExtendedActorSystem actorSystem) {
final String extensionClass;
if (extensionIdConfig.extensionClass() == null) {
// Resolve from default extension config
final ExtensionIdConfig<T> globalConfig = globalConfig(actorSystem);
extensionClass = globalConfig.extensionClass();
if (extensionClass == null) {
throw new DittoConfigError(MessageFormat.format(
"Could not resolve default extension class at config key <{0}>.",
getConfigPath()
));
}
} else {
extensionClass = extensionIdConfig.extensionClass();
}
return extensionClass;
}
private String getConfigPath() {
return ScopedConfig.DITTO_EXTENSIONS_SCOPE + "." + getConfigKey();
}
protected abstract String getConfigKey();
public record ExtensionIdConfig<T extends Extension>(Class<T> parentClass,
@Nullable String extensionClass,
Config extensionConfig) {
private static final String EXTENSION_CLASS = "extension-class";
private static final String EXTENSION_CONFIG = "extension-config";
/**
* @param parentClass the parent class of the extension that should be initialized.
* @param config the config to load the extension from.
* @param configKey the configuration key on root level of the given config.
* @param <T> The type of the extension that should be initialized.
* @return the extension id config.
* @throws com.typesafe.config.ConfigException.WrongType in case neither an object nor a string is
* configured at the config key of config.
*/
public static <T extends org.apache.pekko.actor.Extension> ExtensionIdConfig<T> of(
final Class<T> parentClass,
final Config config,
final String configKey) {
if (config.hasPath(configKey)) {
final var configValue = config.getValue(configKey);
return of(parentClass, configValue);
}
return new ExtensionIdConfig<>(parentClass, null, ConfigFactory.empty());
}
@SuppressWarnings("unchecked")
public static <T extends Extension> ExtensionIdConfig<T> of(final Class<T> parentClass,
final ConfigValue configValue) {
final var valueType = configValue.valueType();
final Object unwrappedValue = configValue.unwrapped();
if (valueType == ConfigValueType.OBJECT) {
// means that the entry is a Map which can be used to create config object from
return ofObjectConfig(parentClass, ConfigFactory.parseMap((Map<String, ?>) unwrappedValue));
} else {
// Allows shorthand configuration by just defining the fqcn if no extension config is desired.
return ofStringConfig(parentClass, (String) unwrappedValue);
}
}
private static <T extends Extension> ExtensionIdConfig<T> ofStringConfig(
final Class<T> parentClass,
final String extensionClass) {
return new ExtensionIdConfig<>(parentClass, extensionClass, ConfigFactory.empty());
}
private static <T extends Extension> ExtensionIdConfig<T> ofObjectConfig(
final Class<T> parentClass,
final Config config) {
@Nullable final String extensionClass;
final Config extensionConfig;
if (config.hasPath(EXTENSION_CLASS)) {
extensionClass = config.getString(EXTENSION_CLASS);
} else {
extensionClass = null;
}
if (config.hasPath(EXTENSION_CONFIG)) {
extensionConfig = config.getConfig(EXTENSION_CONFIG);
} else {
extensionConfig = ConfigFactory.empty();
}
return new ExtensionIdConfig<>(parentClass, extensionClass, extensionConfig);
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final ExtensionIdConfig<?> that = (ExtensionIdConfig<?>) o;
return parentClass.equals(that.parentClass) &&
extensionConfig.equals(that.extensionConfig) &&
Objects.equals(extensionClass, that.extensionClass);
}
@Override
public int hashCode() {
return Objects.hash(parentClass, extensionClass, extensionConfig);
}
@Override
public String toString() {
return getClass().getSimpleName() + " [" +
"parentClass=" + parentClass +
", extensionClass=" + extensionClass +
", extensionConfig=" + extensionConfig +
"]";
}
}
}
}