76: Introduce the Nenjim component registry and move runtime composition into its manager
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.fanitas.evelyn.core;
|
||||
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
@@ -7,7 +8,7 @@ import java.util.List;
|
||||
/**
|
||||
* Collects and exposes the status-index history for one named Evelyn instance.
|
||||
*/
|
||||
public interface Evelyn extends AutoCloseable {
|
||||
public interface Evelyn extends NenjimComponent, AutoCloseable {
|
||||
/**
|
||||
* Executes the legacy Evelyn service operation.
|
||||
*
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
||||
import com.r35157.libs.valuetypes.basic.TradingPair;
|
||||
import com.r35157.assetaz.services.ticker.PriceObservation;
|
||||
import com.r35157.assetaz.services.ticker.TickerService;
|
||||
import com.r35157.nenjim.component.NenjimApplication;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -45,7 +46,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import static com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram.SPL_TOKEN_PROGRAM;
|
||||
import static com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram.TOKEN_2022_PROGRAM;
|
||||
|
||||
public class EvelynImpl implements Evelyn {
|
||||
public class EvelynImpl implements Evelyn, NenjimApplication {
|
||||
|
||||
public EvelynImpl(
|
||||
@NotNull String instanceName,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.fanitas.evelyn.service.burner;
|
||||
|
||||
import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -66,7 +67,7 @@ import java.util.List;
|
||||
* weaken duplicate prevention; operators should reconcile uncertain on-chain
|
||||
* activity before deliberately restarting a suspended or pending service.</p>
|
||||
*/
|
||||
public interface EvelynIOUBurnerService {
|
||||
public interface EvelynIOUBurnerService extends NenjimComponent {
|
||||
/**
|
||||
* Starts the dedicated worker and begins the first iteration immediately.
|
||||
*
|
||||
|
||||
+2
-1
@@ -18,6 +18,7 @@ import com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram;
|
||||
import com.r35157.libs.valuetypes.basic.AssetPrice;
|
||||
import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
||||
import com.r35157.libs.valuetypes.basic.TradingPair;
|
||||
import com.r35157.nenjim.component.NenjimApplication;
|
||||
import com.r35157.service.notification.BoundNotificationService;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
@@ -48,7 +49,7 @@ import static com.r35157.assetaz.services.cis.ExternalCurrencyReference.SOLANA_M
|
||||
* Reference implementation of the Evelyn IOU buyback-and-burn lifecycle.
|
||||
*/
|
||||
public final class EvelynIOUBurnerServiceImpl
|
||||
implements EvelynIOUBurnerService {
|
||||
implements EvelynIOUBurnerService, NenjimApplication {
|
||||
/**
|
||||
* Creates an Evelyn IOU burner with all runtime dependencies and current
|
||||
* operating policy.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.r35157.assetaz.services.cis;
|
||||
|
||||
import com.r35157.assetaz.valuetypes.CurrencyType;
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Set;
|
||||
@@ -20,7 +21,7 @@ import java.util.UUID;
|
||||
* {@link CurrencyType} instances, but callers must compare currency values with
|
||||
* {@link Object#equals(Object)} rather than reference identity.</p>
|
||||
*/
|
||||
public interface CurrencyIdentityService {
|
||||
public interface CurrencyIdentityService extends NenjimComponent {
|
||||
/**
|
||||
* Resolves an AssetAZ currency UUID to its current currency metadata.
|
||||
*
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package com.r35157.assetaz.services.ticker;
|
||||
|
||||
import com.r35157.libs.valuetypes.basic.TradingPair;
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Provides price observations for trading pairs.
|
||||
*/
|
||||
public interface TickerService {
|
||||
public interface TickerService extends NenjimComponent {
|
||||
|
||||
/**
|
||||
* Starts the ticker service.
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package com.r35157.assetaz.services.ticker.impl.ref;
|
||||
|
||||
import com.r35157.assetaz.services.ticker.PriceObservation;
|
||||
import com.r35157.assetaz.services.ticker.plugins.pricesource.PriceSink;
|
||||
import com.r35157.assetaz.services.ticker.plugins.pricesource.PriceSource;
|
||||
import com.r35157.assetaz.services.ticker.pricesource.PriceSink;
|
||||
import com.r35157.assetaz.services.ticker.pricesource.PriceSource;
|
||||
import com.r35157.assetaz.services.ticker.TickerService;
|
||||
import com.r35157.libs.valuetypes.basic.AssetPrice;
|
||||
import com.r35157.libs.valuetypes.basic.TradingPair;
|
||||
import com.r35157.nenjim.component.NenjimApplication;
|
||||
import com.r35157.nenjim.service.registry.NenjimRegistryService;
|
||||
import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -36,11 +39,28 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
public final class TickerServiceImpl implements TickerService, PriceSink {
|
||||
public TickerServiceImpl(PriceSource... priceSources) {
|
||||
public final class TickerServiceImpl implements
|
||||
TickerService,
|
||||
PriceSink,
|
||||
NenjimApplication {
|
||||
public TickerServiceImpl(
|
||||
@NotNull NenjimRegistryService registryService,
|
||||
@NotNull NenjimComponentId... priceSourceIds) {
|
||||
this(DATA_ROOT);
|
||||
|
||||
for (PriceSource priceSource : priceSources) {
|
||||
Objects.requireNonNull(registryService, "registryService");
|
||||
Objects.requireNonNull(priceSourceIds, "priceSourceIds");
|
||||
for (NenjimComponentId priceSourceId : priceSourceIds) {
|
||||
Objects.requireNonNull(priceSourceId, "priceSourceId");
|
||||
PriceSource priceSource = registryService.getComponent(
|
||||
priceSourceId,
|
||||
PriceSource.class);
|
||||
if (priceSource == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Required Nenjim component '" + priceSourceId.value()
|
||||
+ "' is not registered as "
|
||||
+ PriceSource.class.getName());
|
||||
}
|
||||
addPriceSource(priceSource);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.r35157.assetaz.services.ticker.plugins.pricesource;
|
||||
package com.r35157.assetaz.services.ticker.pricesource;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
+3
-2
@@ -1,12 +1,13 @@
|
||||
package com.r35157.assetaz.services.ticker.plugins.pricesource;
|
||||
package com.r35157.assetaz.services.ticker.pricesource;
|
||||
|
||||
import com.r35157.libs.valuetypes.basic.TradingPair;
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Obtains prices for one trading pair and announces them to a {@link PriceSink}.
|
||||
*/
|
||||
public interface PriceSource {
|
||||
public interface PriceSource extends NenjimComponent {
|
||||
|
||||
@NotNull TradingPair getTradingPair();
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
package com.r35157.assetaz.services.ticker.plugins.pricesource.impl.hardcoded;
|
||||
package com.r35157.assetaz.services.ticker.pricesource.impl.hardcoded;
|
||||
|
||||
import com.r35157.assetaz.services.ticker.plugins.pricesource.PriceSink;
|
||||
import com.r35157.assetaz.services.ticker.plugins.pricesource.PriceSource;
|
||||
import com.r35157.assetaz.services.ticker.pricesource.PriceSink;
|
||||
import com.r35157.assetaz.services.ticker.pricesource.PriceSource;
|
||||
import com.r35157.assetaz.services.cis.CurrencyIdentityService;
|
||||
import com.r35157.libs.valuetypes.basic.TradingPair;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
package com.r35157.assetaz.services.ticker.plugins.pricesource.impl.raydiumpool;
|
||||
package com.r35157.assetaz.services.ticker.pricesource.impl.raydiumpool;
|
||||
|
||||
import com.r35157.assetaz.services.ticker.plugins.pricesource.PriceSink;
|
||||
import com.r35157.assetaz.services.ticker.plugins.pricesource.PriceSource;
|
||||
import com.r35157.assetaz.services.ticker.pricesource.PriceSink;
|
||||
import com.r35157.assetaz.services.ticker.pricesource.PriceSource;
|
||||
import com.r35157.libs.raydium.Raydium;
|
||||
import com.r35157.libs.valuetypes.basic.AssetPrice;
|
||||
import com.r35157.libs.valuetypes.basic.TradingPair;
|
||||
@@ -2,6 +2,7 @@ package com.r35157.cryptowallet.solana;
|
||||
|
||||
import com.r35157.libs.solana.SolanaSignedTransaction;
|
||||
import com.r35157.libs.solana.SolanaUnsignedTransaction;
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram;
|
||||
import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -17,7 +18,7 @@ import java.math.BigDecimal;
|
||||
* <p>Signing and submission can be performed separately or as one complete
|
||||
* operation.</p>
|
||||
*/
|
||||
public interface SolanaWallet {
|
||||
public interface SolanaWallet extends NenjimComponent {
|
||||
/**
|
||||
* Returns the public Solana address controlled by this wallet.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.r35157.evelyn.emc;
|
||||
|
||||
public interface EvelynMissionControl {
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
|
||||
public interface EvelynMissionControl extends NenjimComponent {
|
||||
void start();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import com.fanitas.evelyn.core.Evelyn;
|
||||
import com.fanitas.evelyn.core.EvelynStatusIndexPoint;
|
||||
import com.r35157.evelyn.emc.EvelynMissionControl;
|
||||
import com.r35157.libs.javafx.JavaFxRuntime;
|
||||
import com.r35157.nenjim.component.NenjimApplication;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
@@ -26,9 +28,14 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
public final class EvelynMissionControlImpl implements EvelynMissionControl {
|
||||
public final class EvelynMissionControlImpl implements
|
||||
EvelynMissionControl,
|
||||
NenjimApplication {
|
||||
|
||||
public EvelynMissionControlImpl(Evelyn evelynServiceProd, Evelyn evelynServiceTest) {
|
||||
public EvelynMissionControlImpl(
|
||||
@NotNull Evelyn evelynServiceProd,
|
||||
@NotNull Evelyn evelynServiceTest
|
||||
) {
|
||||
this.evelynProd = Objects.requireNonNull(evelynServiceProd, "evelynServiceProd");
|
||||
this.evelynTest = Objects.requireNonNull(evelynServiceTest, "evelynServiceTest");
|
||||
refreshTimeline.setCycleCount(Timeline.INDEFINITE);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.r35157.jupiterperpsalarm;
|
||||
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
|
||||
/**
|
||||
* Monitors configured Jupiter Perps prices and dispatches matching alarm
|
||||
* actions.
|
||||
@@ -9,7 +11,7 @@ package com.r35157.jupiterperpsalarm;
|
||||
* wallet is observed and used for transaction actions; alarm action files do
|
||||
* not select them.</p>
|
||||
*/
|
||||
public interface JupiterPerpsAlarm {
|
||||
public interface JupiterPerpsAlarm extends NenjimComponent {
|
||||
/**
|
||||
* Starts alarm monitoring asynchronously.
|
||||
*
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.r35157.jupiterperpsalarm.impl.ref;
|
||||
import com.r35157.cryptowallet.solana.SolanaWallet;
|
||||
import com.r35157.jupiterperpsalarm.JupiterPerpsAlarm;
|
||||
import com.r35157.libs.jupiter.perps.JupiterPerpsService;
|
||||
import com.r35157.nenjim.component.NenjimApplication;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.net.URI;
|
||||
@@ -15,7 +16,9 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
public final class JupiterPerpsAlarmImpl implements JupiterPerpsAlarm {
|
||||
public final class JupiterPerpsAlarmImpl implements
|
||||
JupiterPerpsAlarm,
|
||||
NenjimApplication {
|
||||
|
||||
public JupiterPerpsAlarmImpl(
|
||||
@NotNull SolanaWallet solanaWallet,
|
||||
@@ -43,7 +46,7 @@ public final class JupiterPerpsAlarmImpl implements JupiterPerpsAlarm {
|
||||
} catch (Throwable throwable) {
|
||||
throwable.printStackTrace(System.err);
|
||||
}
|
||||
}, "Nenjim Plugin - JupiterPerpsAlarm");
|
||||
}, "Nenjim Component - JupiterPerpsAlarm");
|
||||
this.thread.setDaemon(false);
|
||||
}
|
||||
|
||||
@@ -254,9 +257,10 @@ public final class JupiterPerpsAlarmImpl implements JupiterPerpsAlarm {
|
||||
|
||||
private void printUsage() {
|
||||
System.err.println("""
|
||||
JupiterPerpsAlarmImpl is constructed and started by NenjimHubImpl.
|
||||
JupiterPerpsAlarmImpl is constructed and registered by the Nenjim Registry service manager.
|
||||
It is intentionally excluded from the current explicit startup list.
|
||||
|
||||
Current Hub configuration:
|
||||
Current registered configuration:
|
||||
--config=conf/alarms.conf
|
||||
|
||||
Environment:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.r35157.libs.jupiter.perps;
|
||||
|
||||
import com.r35157.libs.jupiter.JupiterTransactionOutcomeException;
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -16,7 +17,7 @@ import java.util.Set;
|
||||
* configured wallet is responsible for signing them. Position operations
|
||||
* return normally only after independent Solana {@code CONFIRMED} success.</p>
|
||||
*/
|
||||
public interface JupiterPerpsService {
|
||||
public interface JupiterPerpsService extends NenjimComponent {
|
||||
/**
|
||||
* Reads a Jupiter Perps position from a known position account.
|
||||
*
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.r35157.libs.jupiter.swap;
|
||||
|
||||
import com.r35157.libs.jupiter.JupiterTransactionOutcomeException;
|
||||
import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -24,7 +25,7 @@ import java.math.BigDecimal;
|
||||
* endpoint, and independently awaits Solana confirmation. Legacy SPL-token
|
||||
* and Token-2022 behavior is unchanged.</p>
|
||||
*/
|
||||
public interface JupiterSwapService {
|
||||
public interface JupiterSwapService extends NenjimComponent {
|
||||
/**
|
||||
* Swaps an exact amount of a supported token or native SOL into an SPL
|
||||
* token.
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package com.r35157.libs.objcache;
|
||||
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Stores objects under structured keys for a caller-supplied time to live.
|
||||
*/
|
||||
public interface ObjectCache {
|
||||
public interface ObjectCache extends NenjimComponent {
|
||||
|
||||
/**
|
||||
* Stores or replaces an object.
|
||||
|
||||
@@ -3,12 +3,13 @@ package com.r35157.libs.raydium;
|
||||
import com.r35157.libs.valuetypes.basic.AssetPrice;
|
||||
import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
||||
import com.r35157.libs.valuetypes.basic.Range;
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Set;
|
||||
|
||||
public interface Raydium {
|
||||
public interface Raydium extends NenjimComponent {
|
||||
/**
|
||||
* Fetches the current price for a Raydium liquidity pool.
|
||||
*
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.r35157.libs.solana;
|
||||
import com.r35157.libs.solana.valuetypes.SolanaProgramDerivedAddress;
|
||||
import com.r35157.libs.solana.valuetypes.economic.SolanaSPLTokenProgram;
|
||||
import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
@@ -21,7 +22,7 @@ import java.util.Set;
|
||||
* logic. Higher-level integrations are expected to interpret Solana accounts,
|
||||
* token holdings and derived addresses according to their own domain rules.</p>
|
||||
*/
|
||||
public interface SolanaBlockChain {
|
||||
public interface SolanaBlockChain extends NenjimComponent {
|
||||
/**
|
||||
* Fetches the native SOL balance for a Solana address.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.r35157.nenjim.component;
|
||||
|
||||
/** A Nenjim component with an active start lifecycle. */
|
||||
public interface NenjimApplication extends NenjimComponent {
|
||||
void start() throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.r35157.nenjim.component;
|
||||
|
||||
/** Marker for a constructed Nenjim component that is ready for use. */
|
||||
public interface NenjimComponent {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.r35157.nenjim.hubd;
|
||||
|
||||
import com.r35157.nenjim.component.NenjimApplication;
|
||||
import com.r35157.nenjim.service.registry.impl.ref.NenjimRegistryServiceManagerImpl;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class Main {
|
||||
private Main() {
|
||||
}
|
||||
|
||||
public static void main(@NotNull String[] args) throws Exception {
|
||||
NenjimApplication registryServiceManager =
|
||||
new NenjimRegistryServiceManagerImpl();
|
||||
registryServiceManager.start();
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.r35157.nenjim.hubd;
|
||||
|
||||
import crypto.r35157.nenjim.NenjimProcess;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Coordinates the lifecycle of Nenjim services and applications.
|
||||
*/
|
||||
public interface NenjimHub {
|
||||
/**
|
||||
* Starts all initialized services and applications in dependency order,
|
||||
* then blocks while the Hub remains online.
|
||||
*
|
||||
* @throws Exception if startup or shutdown waiting fails
|
||||
*/
|
||||
void start() throws Exception;
|
||||
|
||||
/**
|
||||
* Requests startup of the implementation bound to a fully qualified process
|
||||
* interface name in the active context.
|
||||
*
|
||||
* <p>Dynamic binding through this method is not yet implemented by the
|
||||
* reference Hub.</p>
|
||||
*
|
||||
* @param fqInterfaceName fully qualified interface name; the interface must
|
||||
* extend {@link NenjimProcess}
|
||||
*/
|
||||
void startProcess(String fqInterfaceName);
|
||||
|
||||
/**
|
||||
* Provides a map of all running processes managed by the NenjimHub, keyed by the processId.
|
||||
*
|
||||
* @return a map of {@code NenjimProcess} running processes
|
||||
*/
|
||||
HashMap<Integer, NenjimProcess> getRunningProcesses();
|
||||
|
||||
/**
|
||||
* Performs no operation.
|
||||
*/
|
||||
void noop();
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.r35157.nenjim.hubd.impl.ref;
|
||||
|
||||
import com.r35157.nenjim.hubd.NenjimHub;
|
||||
|
||||
public class Main {
|
||||
|
||||
// TODO: Consider if we really need a Main class or we just need to move the main method to NenjimHubImpl?
|
||||
static void main() throws Exception {
|
||||
NenjimHub nenjimHub = new NenjimHubImpl();
|
||||
nenjimHub.start();
|
||||
}
|
||||
}
|
||||
@@ -1,324 +0,0 @@
|
||||
package com.r35157.nenjim.hubd.impl.ref;
|
||||
|
||||
import com.fanitas.evelyn.core.Evelyn;
|
||||
import com.fanitas.evelyn.core.impl.ref.EvelynImpl;
|
||||
import com.fanitas.evelyn.service.burner.EvelynIOUBurnerService;
|
||||
import com.fanitas.evelyn.service.burner.impl.ref.EvelynIOUBurnerServiceImpl;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.r35157.assetaz.services.cis.CurrencyTypeIds;
|
||||
import com.r35157.assetaz.services.ticker.plugins.pricesource.PriceSource;
|
||||
import com.r35157.assetaz.services.ticker.TickerService;
|
||||
import com.r35157.assetaz.services.ticker.plugins.pricesource.impl.hardcoded.HardcodedPriceSource;
|
||||
import com.r35157.assetaz.services.ticker.plugins.pricesource.impl.raydiumpool.RaydiumPoolPriceSource;
|
||||
import com.r35157.assetaz.services.ticker.impl.ref.TickerServiceImpl;
|
||||
import com.r35157.assetaz.services.cis.CurrencyIdentityService;
|
||||
import com.r35157.assetaz.services.cis.impl.hc.HardcodedCurrencyIdentityService;
|
||||
import com.r35157.cryptowallet.solana.SolanaWallet;
|
||||
import com.r35157.cryptowallet.solana.impl.ref.SolanaWalletImpl;
|
||||
import com.r35157.evelyn.emc.EvelynMissionControl;
|
||||
import com.r35157.evelyn.emc.impl.ref.EvelynMissionControlImpl;
|
||||
import com.r35157.jupiterperpsalarm.JupiterPerpsAlarm;
|
||||
import com.r35157.jupiterperpsalarm.impl.ref.JupiterPerpsAlarmImpl;
|
||||
import com.r35157.libs.jupiter.perps.JupiterPerpsService;
|
||||
import com.r35157.libs.jupiter.perps.impl.anchoridl.AnchorIdlJupiterPerpsServiceImpl;
|
||||
import com.r35157.libs.jupiter.swap.JupiterSwapService;
|
||||
import com.r35157.libs.jupiter.swap.impl.ref.JupiterSwapServiceImpl;
|
||||
import com.r35157.libs.objcache.ObjectCache;
|
||||
import com.r35157.libs.objcache.impl.ref.ObjectCacheImpl;
|
||||
import com.r35157.libs.solana.impl.cached.CachedSolanaBlockChain;
|
||||
import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
||||
import com.r35157.nenjim.composer.NenjimComposer;
|
||||
import com.r35157.nenjim.composer.impl.ref.NenjimComposerImpl;
|
||||
import com.r35157.nenjim.hubd.NenjimHub;
|
||||
import com.r35157.nenjim.npm.NenjimProcessManager;
|
||||
import com.r35157.nenjim.npm.impl.ref.NenjimProcessManagerImpl;
|
||||
import com.r35157.nenjim.ntt.NenjimTestTool;
|
||||
import com.r35157.nenjim.ntt.impl.ref.NenjimTestToolImpl;
|
||||
import com.r35157.libs.raydium.Raydium;
|
||||
import com.r35157.libs.raydium.impl.ref.RaydiumImpl;
|
||||
import com.r35157.libs.solana.SolanaBlockChain;
|
||||
import com.r35157.libs.solana.impl.ref.SolanaBlockChainImpl;
|
||||
import com.r35157.libs.valuetypes.basic.TradingPair;
|
||||
import com.r35157.service.notification.BoundNotificationService;
|
||||
import com.r35157.service.notification.impl.discord.DiscordNotificationServiceImpl;
|
||||
import com.r35157.stm.SodaTaskManager;
|
||||
import com.r35157.stm.impl.ref.SodaTaskManagerImpl;
|
||||
import com.r35157.suwimo.hub.client.SuwimoClient;
|
||||
import com.r35157.suwimo.hub.client.impl.j2fx.SuwimoClientImpl;
|
||||
import crypto.r35157.nenjim.NenjimProcess;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import static com.r35157.assetaz.services.cis.CurrencyTypeIds.EVE_ID;
|
||||
import static com.r35157.assetaz.services.cis.CurrencyTypeIds.USDT_ID;
|
||||
|
||||
public class NenjimHubImpl implements NenjimHub {
|
||||
public NenjimHubImpl() {
|
||||
log.info("Initializing NenjimHub...");
|
||||
|
||||
initializeAll();
|
||||
|
||||
nextProcessId = 1;
|
||||
//processesScope = new StructuredTaskScope.ShutdownOnFailure();
|
||||
processes = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws Exception {
|
||||
|
||||
log.info("Starting:");
|
||||
log.info(" Services...");
|
||||
tickerService.start();
|
||||
//jupiterPerpsAlarm.start();
|
||||
evelynServiceProd.start();
|
||||
evelynServiceTest.start();
|
||||
evelynIOUBurnerServiceProd.start();
|
||||
|
||||
log.info(" Applications...");
|
||||
missionControl.start();
|
||||
//nenjimComposer.start();
|
||||
//processManager.start();
|
||||
//testTool.start();
|
||||
//sodaTaskManager.start();
|
||||
//suwimoClient.start();
|
||||
|
||||
System.out.println("Done - Now online!");
|
||||
|
||||
waitForAndShutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<Integer, NenjimProcess> getRunningProcesses() {
|
||||
return processes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void noop() {
|
||||
System.out.println("NenjimHub command: 'noop'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startProcess(String className) {
|
||||
ClassLoader loader = ClassLoader.getSystemClassLoader();
|
||||
|
||||
/*processesScope.fork(() -> {
|
||||
Class<?> clazz = loader.loadClass(className);
|
||||
if (!NenjimProcess.class.isAssignableFrom(clazz)) {
|
||||
throw new IllegalArgumentException("'" + className + "' does NOT implement NenjimProcess");
|
||||
}
|
||||
|
||||
NenjimProcess proc = (NenjimProcess) clazz.getDeclaredConstructor().newInstance();
|
||||
|
||||
processes.put(nextProcessId, proc);
|
||||
nextProcessId++;
|
||||
|
||||
NenjimContext context = new NenjimContext() { };
|
||||
proc.setNenjimProcessContext(context);
|
||||
proc.setNenjimHub(this);
|
||||
|
||||
Thread.currentThread().setName(proc.getProcessName());
|
||||
System.out.println(" Starter Nenjim process '" + proc.getProcessName() + "'");
|
||||
proc.run();
|
||||
|
||||
return null;
|
||||
});*/
|
||||
}
|
||||
|
||||
private void initializeAll() {
|
||||
log.info(" Initializing building blocks...");
|
||||
initializeBuildingBlocks();
|
||||
|
||||
log.info(" Initializing plugins...");
|
||||
initializePlugins();
|
||||
|
||||
log.info(" Initializing services...");
|
||||
initializeServices();
|
||||
|
||||
log.info(" Initializing applications...");
|
||||
initializeApplications();
|
||||
}
|
||||
|
||||
private void initializeBuildingBlocks() {
|
||||
objectCache = new ObjectCacheImpl();
|
||||
clockUTC = Clock.systemUTC();
|
||||
cis = new HardcodedCurrencyIdentityService();
|
||||
httpClient = HttpClient.newHttpClient();
|
||||
objectMapper = new ObjectMapper();
|
||||
solanaBlockChain = new SolanaBlockChainImpl(httpClient, objectMapper, cis);
|
||||
cachedSolanaBlockChain = new CachedSolanaBlockChain(solanaBlockChain, objectCache);
|
||||
raydium = new RaydiumImpl(httpClient, objectMapper, cachedSolanaBlockChain, cis);
|
||||
eveUSDT = new TradingPair(cis.resolve(EVE_ID), cis.resolve(USDT_ID));
|
||||
solanaWalletEvelynPerpsTest = new SolanaWalletImpl(
|
||||
"6NyYEoXmJyBXvU4pvv9aoxZNdyqqtkmPBZFEDkRDHcqp",
|
||||
"evelyn_perps_test",
|
||||
solanaBlockChain
|
||||
);
|
||||
solanaWalletEvelynPerpsProd = new SolanaWalletImpl(
|
||||
"H5Yrdm7B4FHhTxhPw9SVwFzQyTEL6bAPptZuTyRxcepq",
|
||||
"evelyn_perps_prod",
|
||||
solanaBlockChain
|
||||
);
|
||||
solanaWalletEvelynBurnerProd = new SolanaWalletImpl(
|
||||
"5kjVTc1rebYHg56NyTxSSN1XMKqT2E81eWiaSx1CmzmV",
|
||||
"evelyn_burner_prod",
|
||||
solanaBlockChain
|
||||
);
|
||||
}
|
||||
|
||||
private void initializePlugins() {
|
||||
hardcodedPriceSource = new HardcodedPriceSource(cis);
|
||||
raydiumPoolPriceSource = new RaydiumPoolPriceSource(raydium, EVE_USDT_RAYDIUM_POOL_ID, eveUSDT);
|
||||
}
|
||||
|
||||
private void initializeServices() {
|
||||
tickerService = new TickerServiceImpl(
|
||||
//hardcodedPriceSource, // Activating this could be dangerous as other services rely on these prices
|
||||
raydiumPoolPriceSource
|
||||
);
|
||||
|
||||
jupiterServicePerpsProd = new AnchorIdlJupiterPerpsServiceImpl(
|
||||
cachedSolanaBlockChain,
|
||||
solanaWalletEvelynPerpsProd,
|
||||
cis
|
||||
);
|
||||
|
||||
jupiterServicePerpsTest = new AnchorIdlJupiterPerpsServiceImpl(
|
||||
cachedSolanaBlockChain,
|
||||
solanaWalletEvelynPerpsTest,
|
||||
cis
|
||||
);
|
||||
|
||||
jupiterPerpsAlarm = new JupiterPerpsAlarmImpl(
|
||||
solanaWalletEvelynPerpsTest,
|
||||
jupiterServicePerpsTest,
|
||||
"--config=conf/alarms.conf"
|
||||
);
|
||||
|
||||
evelynServiceProd = new EvelynImpl("Production", tickerService, eveUSDT, clockUTC);
|
||||
|
||||
evelynServiceTest = new EvelynImpl("Test", tickerService, eveUSDT, clockUTC);
|
||||
|
||||
jupiterSwapService = new JupiterSwapServiceImpl(solanaBlockChain, solanaWalletEvelynBurnerProd);
|
||||
discordAssetAZTokenNotificationService = new DiscordNotificationServiceImpl(
|
||||
URI.create("https://discordapp.com/api/webhooks/1537799046025187458/RtoNR5HnGmnutsMfM8AKP8mLDVqP1AxpEQ3p2IvBiaqVOjuVWNcShMkIh2D9uT27Mt4A"),
|
||||
"DiscordBot (https://git.r35157.com/r35157/com_r35157_nenjim-hubd-impl_ref, 0.1)",
|
||||
Duration.ofSeconds(10)
|
||||
);
|
||||
|
||||
List<MoneyAmount> burnTokenCandidates = List.of(new MoneyAmount(new ΩAmountΩ("0.1"), cis.resolve(CurrencyTypeIds.USDT_ID)));
|
||||
|
||||
ΩUSDCAmountΩ maximumSwapAmountPerIterationInUSD = new ΩUSDCAmountΩ("0.25");
|
||||
|
||||
evelynIOUBurnerServiceProd = new EvelynIOUBurnerServiceImpl(
|
||||
solanaWalletEvelynBurnerProd,
|
||||
jupiterSwapService,
|
||||
solanaBlockChain,
|
||||
tickerService,
|
||||
cis,
|
||||
discordAssetAZTokenNotificationService,
|
||||
burnTokenCandidates,
|
||||
maximumSwapAmountPerIterationInUSD,
|
||||
200,
|
||||
2*1000*60L,
|
||||
Duration.ofSeconds(10),
|
||||
Duration.ofHours(1)
|
||||
);
|
||||
|
||||
// TODO: Old but more correct way to auto start plugins - but it is currently broken.
|
||||
/*
|
||||
String[] processesToAutoStart = {
|
||||
//"com.r35157.jupiterperpsalarm.impl.ref.JupiterPerpsAlarmImpl"
|
||||
//"crypto.r35157.nenjim.NenjimHubSocketAdminAdapter",
|
||||
//"crypto.r35157.nenjim.NenjimHubRestAdminAdapter",
|
||||
//"crypto.r35157.nenjim.NenjimHubRPCAdminAdapter",
|
||||
//"crypto.r35157.nenjim.SuwimoHub",
|
||||
//"crypto.r35157.nenjim.SodaTaskManager",
|
||||
//"crypto.r35157.assetaz.hub.AssetAZHub"
|
||||
};
|
||||
|
||||
for (String processInterfaceName : processesToAutoStart) {
|
||||
startProcess(processInterfaceName);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
private void initializeApplications() {
|
||||
//nenjimComposer = new NenjimComposerImpl();
|
||||
//processManager = new NenjimProcessManagerImpl();
|
||||
//testTool = new NenjimTestToolImpl();
|
||||
//sodaTaskManager = new SodaTaskManagerImpl();
|
||||
missionControl = new EvelynMissionControlImpl(evelynServiceProd, evelynServiceTest);
|
||||
//suwimoClient = new SuwimoClientImpl();
|
||||
}
|
||||
|
||||
private void waitForAndShutdown() throws InterruptedException {
|
||||
/*
|
||||
try {
|
||||
processesScope.join();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
processesScope.close();
|
||||
*/
|
||||
|
||||
shutdownLatch.await();
|
||||
|
||||
System.out.println("Nenjim is now shutdown (stopped all processes)!");
|
||||
}
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(NenjimHubImpl.class);
|
||||
private static final ΩRaydiumLiquidityPoolIdΩ EVE_USDT_RAYDIUM_POOL_ID =
|
||||
"8rN4BTEzbogQosEQYgsEu18XwfKS5Yoxqwit8zEVFwEe";
|
||||
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
|
||||
|
||||
private HashMap<Integer, NenjimProcess> processes;
|
||||
//private StructuredTaskScope.ShutdownOnFailure processesScope;
|
||||
private int nextProcessId;
|
||||
|
||||
// Dependency components
|
||||
private ObjectCache objectCache;
|
||||
private Clock clockUTC;
|
||||
private CurrencyIdentityService cis;
|
||||
private SolanaBlockChain solanaBlockChain;
|
||||
private SolanaBlockChain cachedSolanaBlockChain;
|
||||
private Raydium raydium;
|
||||
private TradingPair eveUSDT;
|
||||
private HttpClient httpClient;
|
||||
private ObjectMapper objectMapper;
|
||||
private SolanaWallet evelynPerpsProd;
|
||||
private SolanaWallet solanaWalletEvelynPerpsProd;
|
||||
private SolanaWallet solanaWalletEvelynPerpsTest;
|
||||
private SolanaWallet solanaWalletEvelynBurnerProd;
|
||||
private JupiterPerpsService jupiterServicePerpsProd;
|
||||
private JupiterPerpsService jupiterServicePerpsTest;
|
||||
|
||||
// Plugins
|
||||
private PriceSource hardcodedPriceSource;
|
||||
private PriceSource raydiumPoolPriceSource;
|
||||
|
||||
// Services
|
||||
private TickerService tickerService;
|
||||
private JupiterPerpsAlarm jupiterPerpsAlarm;
|
||||
private Evelyn evelynServiceProd;
|
||||
private Evelyn evelynServiceTest;
|
||||
private JupiterSwapService jupiterSwapService;
|
||||
private BoundNotificationService discordAssetAZTokenNotificationService;
|
||||
private EvelynIOUBurnerService evelynIOUBurnerServiceProd;
|
||||
|
||||
// Applications
|
||||
private EvelynMissionControl missionControl;
|
||||
private NenjimComposer nenjimComposer;
|
||||
private NenjimProcessManager processManager;
|
||||
private NenjimTestTool testTool;
|
||||
private SodaTaskManager sodaTaskManager;
|
||||
private SuwimoClient suwimoClient;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.r35157.nenjim.kicker;
|
||||
|
||||
//import com.r35157.nenjim.hubd.Context;
|
||||
import com.r35157.nenjim.hubd.NenjimHub;
|
||||
import crypto.r35157.nenjim.NenjimHub;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface NenjimProcess {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.r35157.nenjim.kicker.impl.ref;
|
||||
|
||||
import com.r35157.nenjim.hubd.NenjimHub;
|
||||
import com.r35157.nenjim.hubd.ctx.Context;
|
||||
import com.r35157.nenjim.kicker.NenjimKicker;
|
||||
|
||||
@@ -34,7 +33,6 @@ public final class Initializer {
|
||||
private void startNenjimKicker(Path pathToClassCache) throws Exception {
|
||||
System.out.println("Initializing NenjimKicker...");
|
||||
|
||||
NenjimHub nenjimHub = null;
|
||||
Context defaultContext = new Context();
|
||||
NenjimClassLoader nenjimClassLoader = new NenjimClassLoader(defaultContext, pathToClassCache);
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.r35157.nenjim.kicker.impl.ref;
|
||||
|
||||
//import com.r35157.nenjim.hubd.Context;
|
||||
import com.r35157.nenjim.hubd.NenjimHub;
|
||||
import com.r35157.nenjim.kicker.NenjimProcess;
|
||||
import crypto.r35157.nenjim.NenjimHub;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class NenjimProcessImpl implements NenjimProcess {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.r35157.nenjim.service.registry;
|
||||
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** Read-only catalogue of constructed Nenjim components. */
|
||||
public interface NenjimRegistryService extends NenjimComponent {
|
||||
@NotNull
|
||||
List<NenjimComponentId> getComponentIds(
|
||||
@NotNull Class<? extends NenjimComponent> componentInterface);
|
||||
|
||||
@Nullable
|
||||
<T extends NenjimComponent> T getComponent(
|
||||
@NotNull NenjimComponentId componentId,
|
||||
@NotNull Class<T> expectedInterface);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.r35157.nenjim.service.registry;
|
||||
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
|
||||
/** Lifecycle owner for the Nenjim Registry and hardcoded component graph. */
|
||||
public interface NenjimRegistryServiceManager extends NenjimComponent {
|
||||
void start() throws Exception;
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.r35157.nenjim.service.registry.impl.ref;
|
||||
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/** Internal mutation handle retained only by the Registry service manager. */
|
||||
interface NenjimRegistryServiceAdmin {
|
||||
void registerComponent(
|
||||
@NotNull NenjimComponentId componentId,
|
||||
@NotNull NenjimComponent component);
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
package com.r35157.nenjim.service.registry.impl.ref;
|
||||
|
||||
import com.r35157.nenjim.component.NenjimApplication;
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import com.r35157.nenjim.service.registry.NenjimRegistryService;
|
||||
import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/** Reference Registry storage with read-only public and internal admin views. */
|
||||
final class NenjimRegistryServiceImpl implements
|
||||
NenjimRegistryService,
|
||||
NenjimRegistryServiceAdmin,
|
||||
NenjimApplication {
|
||||
@Override
|
||||
public synchronized void start() {
|
||||
if (startAttempted) {
|
||||
throw new IllegalStateException(
|
||||
"Nenjim Registry service start has already been attempted");
|
||||
}
|
||||
|
||||
startAttempted = true;
|
||||
started = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void registerComponent(
|
||||
@NotNull NenjimComponentId componentId,
|
||||
@NotNull NenjimComponent component) {
|
||||
Objects.requireNonNull(componentId, "componentId");
|
||||
Objects.requireNonNull(component, "component");
|
||||
|
||||
NenjimComponent existingComponent = componentsById.get(componentId);
|
||||
if (existingComponent != null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Duplicate Nenjim component ID '" + componentId.value()
|
||||
+ "': existing implementation "
|
||||
+ existingComponent.getClass().getName()
|
||||
+ ", attempted implementation "
|
||||
+ component.getClass().getName());
|
||||
}
|
||||
|
||||
Set<Class<? extends NenjimComponent>> componentInterfaces =
|
||||
findComponentInterfaces(component.getClass());
|
||||
|
||||
componentsById.put(componentId, component);
|
||||
for (Class<? extends NenjimComponent> componentInterface
|
||||
: componentInterfaces) {
|
||||
componentIdsByInterface
|
||||
.computeIfAbsent(
|
||||
componentInterface,
|
||||
ignored -> new ArrayList<>())
|
||||
.add(componentId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized @NotNull List<NenjimComponentId> getComponentIds(
|
||||
@NotNull Class<? extends NenjimComponent> componentInterface) {
|
||||
validateComponentInterface(componentInterface);
|
||||
requireStarted();
|
||||
|
||||
List<NenjimComponentId> componentIds =
|
||||
componentIdsByInterface.get(componentInterface);
|
||||
return componentIds == null ? List.of() : List.copyOf(componentIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized @Nullable <T extends NenjimComponent> T getComponent(
|
||||
@NotNull NenjimComponentId componentId,
|
||||
@NotNull Class<T> expectedInterface) {
|
||||
Objects.requireNonNull(componentId, "componentId");
|
||||
validateComponentInterface(expectedInterface);
|
||||
requireStarted();
|
||||
|
||||
NenjimComponent component = componentsById.get(componentId);
|
||||
if (component == null) {
|
||||
return null;
|
||||
}
|
||||
if (!expectedInterface.isInstance(component)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Nenjim component '" + componentId.value()
|
||||
+ "' was requested as " + expectedInterface.getName()
|
||||
+ " but is implemented by "
|
||||
+ component.getClass().getName());
|
||||
}
|
||||
return expectedInterface.cast(component);
|
||||
}
|
||||
|
||||
private void requireStarted() {
|
||||
if (!started) {
|
||||
throw new IllegalStateException(
|
||||
"Nenjim Registry service has not started successfully");
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateComponentInterface(@NotNull Class<?> type) {
|
||||
Objects.requireNonNull(type, "componentInterface");
|
||||
if (!type.isInterface()
|
||||
|| !NenjimComponent.class.isAssignableFrom(type)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Expected an interface extending "
|
||||
+ NenjimComponent.class.getName()
|
||||
+ ", but received " + type.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private static @NotNull Set<Class<? extends NenjimComponent>>
|
||||
findComponentInterfaces(@NotNull Class<?> implementationClass) {
|
||||
LinkedHashSet<Class<? extends NenjimComponent>> componentInterfaces =
|
||||
new LinkedHashSet<>();
|
||||
|
||||
for (Class<?> currentClass = implementationClass;
|
||||
currentClass != null;
|
||||
currentClass = currentClass.getSuperclass()) {
|
||||
for (Class<?> implementedInterface : currentClass.getInterfaces()) {
|
||||
collectComponentInterfaces(
|
||||
implementedInterface,
|
||||
componentInterfaces);
|
||||
}
|
||||
}
|
||||
return componentInterfaces;
|
||||
}
|
||||
|
||||
private static void collectComponentInterfaces(
|
||||
@NotNull Class<?> interfaceType,
|
||||
@NotNull Set<Class<? extends NenjimComponent>> destination) {
|
||||
if (NenjimComponent.class.isAssignableFrom(interfaceType)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends NenjimComponent> componentInterface =
|
||||
(Class<? extends NenjimComponent>) interfaceType;
|
||||
destination.add(componentInterface);
|
||||
}
|
||||
|
||||
for (Class<?> parentInterface : interfaceType.getInterfaces()) {
|
||||
collectComponentInterfaces(parentInterface, destination);
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<NenjimComponentId, NenjimComponent> componentsById =
|
||||
new LinkedHashMap<>();
|
||||
private final Map<Class<? extends NenjimComponent>, List<NenjimComponentId>>
|
||||
componentIdsByInterface = new LinkedHashMap<>();
|
||||
private boolean startAttempted;
|
||||
private boolean started;
|
||||
}
|
||||
+351
@@ -0,0 +1,351 @@
|
||||
package com.r35157.nenjim.service.registry.impl.ref;
|
||||
|
||||
import com.fanitas.evelyn.core.Evelyn;
|
||||
import com.fanitas.evelyn.core.impl.ref.EvelynImpl;
|
||||
import com.fanitas.evelyn.service.burner.EvelynIOUBurnerService;
|
||||
import com.fanitas.evelyn.service.burner.impl.ref.EvelynIOUBurnerServiceImpl;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.r35157.assetaz.services.cis.CurrencyTypeIds;
|
||||
import com.r35157.assetaz.services.ticker.pricesource.PriceSource;
|
||||
import com.r35157.assetaz.services.ticker.TickerService;
|
||||
import com.r35157.assetaz.services.ticker.pricesource.impl.hardcoded.HardcodedPriceSource;
|
||||
import com.r35157.assetaz.services.ticker.pricesource.impl.raydiumpool.RaydiumPoolPriceSource;
|
||||
import com.r35157.assetaz.services.ticker.impl.ref.TickerServiceImpl;
|
||||
import com.r35157.assetaz.services.cis.CurrencyIdentityService;
|
||||
import com.r35157.assetaz.services.cis.impl.hc.HardcodedCurrencyIdentityService;
|
||||
import com.r35157.cryptowallet.solana.SolanaWallet;
|
||||
import com.r35157.cryptowallet.solana.impl.ref.SolanaWalletImpl;
|
||||
import com.r35157.evelyn.emc.EvelynMissionControl;
|
||||
import com.r35157.evelyn.emc.impl.ref.EvelynMissionControlImpl;
|
||||
import com.r35157.jupiterperpsalarm.JupiterPerpsAlarm;
|
||||
import com.r35157.jupiterperpsalarm.impl.ref.JupiterPerpsAlarmImpl;
|
||||
import com.r35157.libs.jupiter.perps.JupiterPerpsService;
|
||||
import com.r35157.libs.jupiter.perps.impl.anchoridl.AnchorIdlJupiterPerpsServiceImpl;
|
||||
import com.r35157.libs.jupiter.swap.JupiterSwapService;
|
||||
import com.r35157.libs.jupiter.swap.impl.ref.JupiterSwapServiceImpl;
|
||||
import com.r35157.libs.objcache.ObjectCache;
|
||||
import com.r35157.libs.objcache.impl.ref.ObjectCacheImpl;
|
||||
import com.r35157.libs.solana.impl.cached.CachedSolanaBlockChain;
|
||||
import com.r35157.libs.valuetypes.basic.MoneyAmount;
|
||||
import com.r35157.nenjim.component.NenjimApplication;
|
||||
import com.r35157.nenjim.service.registry.NenjimRegistryService;
|
||||
import com.r35157.nenjim.service.registry.NenjimRegistryServiceManager;
|
||||
import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId;
|
||||
import com.r35157.libs.raydium.Raydium;
|
||||
import com.r35157.libs.raydium.impl.ref.RaydiumImpl;
|
||||
import com.r35157.libs.solana.SolanaBlockChain;
|
||||
import com.r35157.libs.solana.impl.ref.SolanaBlockChainImpl;
|
||||
import com.r35157.libs.valuetypes.basic.TradingPair;
|
||||
import com.r35157.service.notification.BoundNotificationService;
|
||||
import com.r35157.service.notification.impl.discord.DiscordNotificationServiceImpl;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static com.r35157.assetaz.services.cis.CurrencyTypeIds.EVE_ID;
|
||||
import static com.r35157.assetaz.services.cis.CurrencyTypeIds.USDT_ID;
|
||||
|
||||
public final class NenjimRegistryServiceManagerImpl implements
|
||||
NenjimRegistryServiceManager,
|
||||
NenjimApplication {
|
||||
@Override
|
||||
public void start() throws Exception {
|
||||
if (!startAttempted.compareAndSet(false, true)) {
|
||||
throw new IllegalStateException(
|
||||
"Nenjim Registry service manager start has already been attempted");
|
||||
}
|
||||
|
||||
log.info("Initializing Nenjim Registry and components...");
|
||||
NenjimRegistryServiceImpl registry = new NenjimRegistryServiceImpl();
|
||||
NenjimRegistryService registryService = registry;
|
||||
NenjimRegistryServiceAdmin registryAdmin = registry;
|
||||
NenjimApplication registryApplication = registry;
|
||||
|
||||
registryApplication.start();
|
||||
registryAdmin.registerComponent(REGISTRY_SERVICE_ID, registry);
|
||||
registryAdmin.registerComponent(REGISTRY_SERVICE_MANAGER_ID, this);
|
||||
initializeAndRegisterComponents(registryService, registryAdmin);
|
||||
|
||||
log.info("Starting explicitly enabled Nenjim applications...");
|
||||
startApplication(registryService, TICKER_SERVICE_ID);
|
||||
startApplication(registryService, EVELYN_SERVICE_PROD_ID);
|
||||
startApplication(registryService, EVELYN_SERVICE_TEST_ID);
|
||||
startApplication(registryService, EVELYN_IOU_BURNER_PROD_ID);
|
||||
startApplication(registryService, EVELYN_MISSION_CONTROL_ID);
|
||||
|
||||
System.out.println("Done - Now online!");
|
||||
|
||||
waitForAndShutdown();
|
||||
}
|
||||
|
||||
private void initializeAndRegisterComponents(
|
||||
NenjimRegistryService registryService,
|
||||
NenjimRegistryServiceAdmin registryAdmin) {
|
||||
initializeBuildingBlocks(registryAdmin);
|
||||
initializePriceSources(registryAdmin);
|
||||
initializeServices(registryService, registryAdmin);
|
||||
initializeApplications(registryAdmin);
|
||||
}
|
||||
|
||||
private void initializeBuildingBlocks(
|
||||
NenjimRegistryServiceAdmin registryAdmin) {
|
||||
objectCache = new ObjectCacheImpl();
|
||||
registryAdmin.registerComponent(OBJECT_CACHE_ID, objectCache);
|
||||
|
||||
clockUTC = Clock.systemUTC();
|
||||
cis = new HardcodedCurrencyIdentityService();
|
||||
registryAdmin.registerComponent(CURRENCY_IDENTITY_SERVICE_ID, cis);
|
||||
|
||||
httpClient = HttpClient.newHttpClient();
|
||||
objectMapper = new ObjectMapper();
|
||||
solanaBlockChain = new SolanaBlockChainImpl(httpClient, objectMapper, cis);
|
||||
registryAdmin.registerComponent(SOLANA_BLOCKCHAIN_DIRECT_ID, solanaBlockChain);
|
||||
|
||||
cachedSolanaBlockChain = new CachedSolanaBlockChain(solanaBlockChain, objectCache);
|
||||
registryAdmin.registerComponent(SOLANA_BLOCKCHAIN_CACHED_ID, cachedSolanaBlockChain);
|
||||
|
||||
raydium = new RaydiumImpl(httpClient, objectMapper, cachedSolanaBlockChain, cis);
|
||||
registryAdmin.registerComponent(RAYDIUM_SERVICE_ID, raydium);
|
||||
|
||||
eveUSDT = new TradingPair(cis.resolve(EVE_ID), cis.resolve(USDT_ID));
|
||||
solanaWalletEvelynPerpsTest = new SolanaWalletImpl(
|
||||
"6NyYEoXmJyBXvU4pvv9aoxZNdyqqtkmPBZFEDkRDHcqp",
|
||||
"evelyn_perps_test",
|
||||
solanaBlockChain
|
||||
);
|
||||
registryAdmin.registerComponent(
|
||||
SOLANA_WALLET_EVELYN_PERPS_TEST_ID,
|
||||
solanaWalletEvelynPerpsTest);
|
||||
|
||||
solanaWalletEvelynPerpsProd = new SolanaWalletImpl(
|
||||
"H5Yrdm7B4FHhTxhPw9SVwFzQyTEL6bAPptZuTyRxcepq",
|
||||
"evelyn_perps_prod",
|
||||
solanaBlockChain
|
||||
);
|
||||
registryAdmin.registerComponent(
|
||||
SOLANA_WALLET_EVELYN_PERPS_PROD_ID,
|
||||
solanaWalletEvelynPerpsProd);
|
||||
|
||||
solanaWalletEvelynBurnerProd = new SolanaWalletImpl(
|
||||
"5kjVTc1rebYHg56NyTxSSN1XMKqT2E81eWiaSx1CmzmV",
|
||||
"evelyn_burner_prod",
|
||||
solanaBlockChain
|
||||
);
|
||||
registryAdmin.registerComponent(
|
||||
SOLANA_WALLET_EVELYN_BURNER_PROD_ID,
|
||||
solanaWalletEvelynBurnerProd);
|
||||
}
|
||||
|
||||
private void initializePriceSources(
|
||||
NenjimRegistryServiceAdmin registryAdmin) {
|
||||
hardcodedPriceSource = new HardcodedPriceSource(cis);
|
||||
registryAdmin.registerComponent(
|
||||
HARDCODED_PRICE_SOURCE_ID,
|
||||
hardcodedPriceSource);
|
||||
|
||||
raydiumPoolPriceSource = new RaydiumPoolPriceSource(raydium, EVE_USDT_RAYDIUM_POOL_ID, eveUSDT);
|
||||
registryAdmin.registerComponent(
|
||||
RAYDIUM_POOL_PRICE_SOURCE_ID,
|
||||
raydiumPoolPriceSource);
|
||||
}
|
||||
|
||||
private void initializeServices(
|
||||
NenjimRegistryService registryService,
|
||||
NenjimRegistryServiceAdmin registryAdmin) {
|
||||
tickerService = new TickerServiceImpl(
|
||||
registryService,
|
||||
RAYDIUM_POOL_PRICE_SOURCE_ID
|
||||
);
|
||||
registryAdmin.registerComponent(TICKER_SERVICE_ID, tickerService);
|
||||
|
||||
jupiterServicePerpsProd = new AnchorIdlJupiterPerpsServiceImpl(
|
||||
cachedSolanaBlockChain,
|
||||
solanaWalletEvelynPerpsProd,
|
||||
cis
|
||||
);
|
||||
registryAdmin.registerComponent(
|
||||
JUPITER_PERPS_EVELYN_PROD_ID,
|
||||
jupiterServicePerpsProd);
|
||||
|
||||
jupiterServicePerpsTest = new AnchorIdlJupiterPerpsServiceImpl(
|
||||
cachedSolanaBlockChain,
|
||||
solanaWalletEvelynPerpsTest,
|
||||
cis
|
||||
);
|
||||
registryAdmin.registerComponent(
|
||||
JUPITER_PERPS_EVELYN_TEST_ID,
|
||||
jupiterServicePerpsTest);
|
||||
|
||||
jupiterPerpsAlarm = new JupiterPerpsAlarmImpl(
|
||||
solanaWalletEvelynPerpsTest,
|
||||
jupiterServicePerpsTest,
|
||||
"--config=conf/alarms.conf"
|
||||
);
|
||||
registryAdmin.registerComponent(
|
||||
JUPITER_PERPS_ALARM_ID,
|
||||
jupiterPerpsAlarm);
|
||||
|
||||
evelynServiceProd = new EvelynImpl("Production", tickerService, eveUSDT, clockUTC);
|
||||
registryAdmin.registerComponent(EVELYN_SERVICE_PROD_ID, evelynServiceProd);
|
||||
|
||||
evelynServiceTest = new EvelynImpl("Test", tickerService, eveUSDT, clockUTC);
|
||||
registryAdmin.registerComponent(EVELYN_SERVICE_TEST_ID, evelynServiceTest);
|
||||
|
||||
jupiterSwapService = new JupiterSwapServiceImpl(solanaBlockChain, solanaWalletEvelynBurnerProd);
|
||||
registryAdmin.registerComponent(
|
||||
JUPITER_SWAP_EVELYN_BURNER_PROD_ID,
|
||||
jupiterSwapService);
|
||||
discordAssetAZTokenNotificationService = new DiscordNotificationServiceImpl(
|
||||
URI.create("https://discordapp.com/api/webhooks/1537799046025187458/RtoNR5HnGmnutsMfM8AKP8mLDVqP1AxpEQ3p2IvBiaqVOjuVWNcShMkIh2D9uT27Mt4A"),
|
||||
"DiscordBot (https://git.r35157.com/r35157/com_r35157_nenjim-hubd-impl_ref, 0.1)",
|
||||
Duration.ofSeconds(10)
|
||||
);
|
||||
|
||||
registryAdmin.registerComponent(
|
||||
DISCORD_ASSETAZ_TOKEN_NOTIFICATION_ID,
|
||||
discordAssetAZTokenNotificationService);
|
||||
|
||||
List<MoneyAmount> burnTokenCandidates = List.of(new MoneyAmount(new ΩAmountΩ("0.1"), cis.resolve(CurrencyTypeIds.USDT_ID)));
|
||||
|
||||
ΩUSDCAmountΩ maximumSwapAmountPerIterationInUSD = new ΩUSDCAmountΩ("0.25");
|
||||
|
||||
evelynIOUBurnerServiceProd = new EvelynIOUBurnerServiceImpl(
|
||||
solanaWalletEvelynBurnerProd,
|
||||
jupiterSwapService,
|
||||
solanaBlockChain,
|
||||
tickerService,
|
||||
cis,
|
||||
discordAssetAZTokenNotificationService,
|
||||
burnTokenCandidates,
|
||||
maximumSwapAmountPerIterationInUSD,
|
||||
200,
|
||||
2*1000*60L,
|
||||
Duration.ofSeconds(10),
|
||||
Duration.ofHours(1)
|
||||
);
|
||||
registryAdmin.registerComponent(
|
||||
EVELYN_IOU_BURNER_PROD_ID,
|
||||
evelynIOUBurnerServiceProd);
|
||||
}
|
||||
|
||||
private void initializeApplications(
|
||||
NenjimRegistryServiceAdmin registryAdmin) {
|
||||
missionControl = new EvelynMissionControlImpl(evelynServiceProd, evelynServiceTest);
|
||||
registryAdmin.registerComponent(
|
||||
EVELYN_MISSION_CONTROL_ID,
|
||||
missionControl);
|
||||
}
|
||||
|
||||
private static void startApplication(
|
||||
NenjimRegistryService registryService,
|
||||
NenjimComponentId componentId) throws Exception {
|
||||
NenjimApplication application = registryService.getComponent(
|
||||
componentId,
|
||||
NenjimApplication.class);
|
||||
if (application == null) {
|
||||
throw new IllegalStateException(
|
||||
"Required Nenjim application '" + componentId.value()
|
||||
+ "' is not registered as "
|
||||
+ NenjimApplication.class.getName());
|
||||
}
|
||||
application.start();
|
||||
}
|
||||
|
||||
private void waitForAndShutdown() throws InterruptedException {
|
||||
shutdownLatch.await();
|
||||
|
||||
System.out.println("Nenjim is now shut down!");
|
||||
}
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(
|
||||
NenjimRegistryServiceManagerImpl.class);
|
||||
private static final NenjimComponentId REGISTRY_SERVICE_ID =
|
||||
new NenjimComponentId("nenjim.registry.service");
|
||||
private static final NenjimComponentId REGISTRY_SERVICE_MANAGER_ID =
|
||||
new NenjimComponentId("nenjim.registry.service-manager");
|
||||
private static final NenjimComponentId OBJECT_CACHE_ID =
|
||||
new NenjimComponentId("nenjim.object-cache.default");
|
||||
private static final NenjimComponentId CURRENCY_IDENTITY_SERVICE_ID =
|
||||
new NenjimComponentId("assetaz.currency-identity.hardcoded");
|
||||
private static final NenjimComponentId SOLANA_BLOCKCHAIN_DIRECT_ID =
|
||||
new NenjimComponentId("solana.blockchain.direct");
|
||||
private static final NenjimComponentId SOLANA_BLOCKCHAIN_CACHED_ID =
|
||||
new NenjimComponentId("solana.blockchain.cached");
|
||||
private static final NenjimComponentId RAYDIUM_SERVICE_ID =
|
||||
new NenjimComponentId("raydium.service.default");
|
||||
private static final NenjimComponentId SOLANA_WALLET_EVELYN_PERPS_TEST_ID =
|
||||
new NenjimComponentId("solana.wallet.evelyn-perps-test");
|
||||
private static final NenjimComponentId SOLANA_WALLET_EVELYN_PERPS_PROD_ID =
|
||||
new NenjimComponentId("solana.wallet.evelyn-perps-prod");
|
||||
private static final NenjimComponentId SOLANA_WALLET_EVELYN_BURNER_PROD_ID =
|
||||
new NenjimComponentId("solana.wallet.evelyn-burner-prod");
|
||||
private static final NenjimComponentId HARDCODED_PRICE_SOURCE_ID =
|
||||
new NenjimComponentId("assetaz.price-source.hardcoded");
|
||||
private static final NenjimComponentId RAYDIUM_POOL_PRICE_SOURCE_ID =
|
||||
new NenjimComponentId(
|
||||
"assetaz.price-source.raydium-pool.eve-usdt");
|
||||
private static final NenjimComponentId TICKER_SERVICE_ID =
|
||||
new NenjimComponentId("assetaz.ticker.default");
|
||||
private static final NenjimComponentId JUPITER_PERPS_EVELYN_PROD_ID =
|
||||
new NenjimComponentId("jupiter.perps.evelyn-prod");
|
||||
private static final NenjimComponentId JUPITER_PERPS_EVELYN_TEST_ID =
|
||||
new NenjimComponentId("jupiter.perps.evelyn-test");
|
||||
private static final NenjimComponentId JUPITER_PERPS_ALARM_ID =
|
||||
new NenjimComponentId("jupiter-perps-alarm.default");
|
||||
private static final NenjimComponentId EVELYN_SERVICE_PROD_ID =
|
||||
new NenjimComponentId("evelyn.service.prod");
|
||||
private static final NenjimComponentId EVELYN_SERVICE_TEST_ID =
|
||||
new NenjimComponentId("evelyn.service.test");
|
||||
private static final NenjimComponentId JUPITER_SWAP_EVELYN_BURNER_PROD_ID =
|
||||
new NenjimComponentId("jupiter.swap.evelyn-burner-prod");
|
||||
private static final NenjimComponentId DISCORD_ASSETAZ_TOKEN_NOTIFICATION_ID =
|
||||
new NenjimComponentId("notification.discord.assetaz-token");
|
||||
private static final NenjimComponentId EVELYN_IOU_BURNER_PROD_ID =
|
||||
new NenjimComponentId("evelyn.iou-burner.prod");
|
||||
private static final NenjimComponentId EVELYN_MISSION_CONTROL_ID =
|
||||
new NenjimComponentId("evelyn.mission-control.default");
|
||||
private static final ΩRaydiumLiquidityPoolIdΩ EVE_USDT_RAYDIUM_POOL_ID =
|
||||
"8rN4BTEzbogQosEQYgsEu18XwfKS5Yoxqwit8zEVFwEe";
|
||||
private final CountDownLatch shutdownLatch = new CountDownLatch(1);
|
||||
private final AtomicBoolean startAttempted = new AtomicBoolean();
|
||||
|
||||
// Dependency components
|
||||
private ObjectCache objectCache;
|
||||
private Clock clockUTC;
|
||||
private CurrencyIdentityService cis;
|
||||
private SolanaBlockChain solanaBlockChain;
|
||||
private SolanaBlockChain cachedSolanaBlockChain;
|
||||
private Raydium raydium;
|
||||
private TradingPair eveUSDT;
|
||||
private HttpClient httpClient;
|
||||
private ObjectMapper objectMapper;
|
||||
private SolanaWallet solanaWalletEvelynPerpsProd;
|
||||
private SolanaWallet solanaWalletEvelynPerpsTest;
|
||||
private SolanaWallet solanaWalletEvelynBurnerProd;
|
||||
private JupiterPerpsService jupiterServicePerpsProd;
|
||||
private JupiterPerpsService jupiterServicePerpsTest;
|
||||
|
||||
// Price-source components
|
||||
private PriceSource hardcodedPriceSource;
|
||||
private PriceSource raydiumPoolPriceSource;
|
||||
|
||||
// Services
|
||||
private TickerService tickerService;
|
||||
private JupiterPerpsAlarm jupiterPerpsAlarm;
|
||||
private Evelyn evelynServiceProd;
|
||||
private Evelyn evelynServiceTest;
|
||||
private JupiterSwapService jupiterSwapService;
|
||||
private BoundNotificationService discordAssetAZTokenNotificationService;
|
||||
private EvelynIOUBurnerService evelynIOUBurnerServiceProd;
|
||||
|
||||
// Application components
|
||||
private EvelynMissionControl missionControl;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.r35157.nenjim.service.registry.valuetypes;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/** Lookup identity assigned to one component registration in a Registry. */
|
||||
public record NenjimComponentId(@NotNull String value) {
|
||||
public NenjimComponentId {
|
||||
value = value.strip();
|
||||
|
||||
if (!value.matches(
|
||||
"[a-z][a-z0-9]*(?:-[a-z0-9]+)*(?:\\.[a-z][a-z0-9]*(?:-[a-z0-9]+)*)*")) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid Nenjim component ID: '" + value + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.r35157.service.notification;
|
||||
|
||||
import com.r35157.nenjim.component.NenjimComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -12,7 +13,7 @@ import java.io.IOException;
|
||||
* or how its destination is configured.</p>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BoundNotificationService {
|
||||
public interface BoundNotificationService extends NenjimComponent {
|
||||
/**
|
||||
* Sends a notification message.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user