-
Notifications
You must be signed in to change notification settings - Fork 215
/
DittoExtensionPoint.java
188 lines (161 loc) · 7.57 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
/*
* 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.base.service;
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.akka.AkkaClassLoader;
import org.eclipse.ditto.internal.utils.config.DittoConfigError;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import akka.actor.AbstractExtensionId;
import akka.actor.ActorSystem;
import akka.actor.ExtendedActorSystem;
import akka.actor.Extension;
/**
* Extension Point for extending Ditto via Akka 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 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;
}
protected ExtensionId(final Class<T> parentClass) {
this(new ExtensionIdConfig<>(parentClass, null, ConfigFactory.empty()));
}
@Override
public T createExtension(final ExtendedActorSystem system) {
return AkkaClassLoader.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
//TODO: Yannic make getConfigPath returning just the config key and define the constant "ditto.extensions." just one time in this class.
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;
}
protected abstract String getConfigPath();
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 Extension> ExtensionIdConfig<T> of(
final Class<T> parentClass,
final Config config,
final String configKey) {
if (config.hasPath(configKey)) {
final Object anyRef = config.getAnyRef(configKey);
if (anyRef instanceof Map) {
// means that the entry is a config object
return ofObjectConfig(parentClass, config.getConfig(configKey));
} else {
// Allows shorthand configuration by just defining the fqcn if no extension config is desired.
return ofStringConfig(parentClass, config.getString(configKey));
}
}
return new ExtensionIdConfig<>(parentClass, null, ConfigFactory.empty());
}
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 +
"]";
}
}
}
}