1+ package streams
2+
3+ import kotlinx.coroutines.Dispatchers
4+ import kotlinx.coroutines.GlobalScope
5+ import kotlinx.coroutines.launch
6+ import kotlinx.coroutines.runBlocking
7+ import kotlinx.coroutines.sync.Mutex
8+ import kotlinx.coroutines.sync.withLock
9+ import org.neo4j.kernel.availability.AvailabilityListener
10+ import org.neo4j.kernel.internal.GraphDatabaseAPI
11+ import streams.procedures.StreamsSinkProcedures
12+ import streams.service.TopicUtils
13+ import streams.utils.Neo4jUtils
14+ import streams.utils.StreamsUtils
15+ import java.util.concurrent.ConcurrentHashMap
16+
17+ class StreamsEventSinkAvailabilityListener (dependencies : StreamsEventSinkExtensionFactory .Dependencies ): AvailabilityListener {
18+ private val db = dependencies.graphdatabaseAPI()
19+ private val logService = dependencies.log()
20+ private val configuration = dependencies.config()
21+ private val log = logService.getUserLog(StreamsEventSinkAvailabilityListener ::class .java)
22+
23+ private var eventSink: StreamsEventSink ? = null
24+ private val streamsSinkConfiguration: StreamsSinkConfiguration
25+ private val streamsTopicService: StreamsTopicService
26+ private val streamsQueryExecution: StreamsEventSinkQueryExecution
27+
28+ private val mutex = Mutex ()
29+
30+ init {
31+ streamsSinkConfiguration = StreamsSinkConfiguration .from(configuration)
32+ streamsTopicService = StreamsTopicService (db)
33+ streamsTopicService.setAll(streamsSinkConfiguration.topics)
34+ val strategyMap = TopicUtils .toStrategyMap(streamsSinkConfiguration.topics,
35+ streamsSinkConfiguration.sourceIdStrategyConfig)
36+ streamsQueryExecution = StreamsEventSinkQueryExecution (streamsTopicService, db,
37+ logService.getUserLog(StreamsEventSinkQueryExecution ::class .java),
38+ strategyMap)
39+ }
40+
41+
42+ override fun available () {
43+ runBlocking {
44+ mutex.withLock {
45+ setAvailable(db, true )
46+ if (eventSink == null ) {
47+ // Create the Sink if not exists
48+ eventSink = StreamsEventSinkFactory
49+ .getStreamsEventSink(configuration,
50+ streamsQueryExecution,
51+ streamsTopicService,
52+ log,
53+ db)
54+ }
55+ }
56+ }
57+ try {
58+ log.info(" Initialising the Streams Sink module" )
59+
60+ // start the Sink
61+ if (Neo4jUtils .isCluster(db, log)) {
62+ log.info(" The Sink module is running in a cluster, checking for the ${Neo4jUtils .LEADER } " )
63+ Neo4jUtils .waitForTheLeader(db, log) { initSinkModule() }
64+ } else {
65+ runInASingleInstance()
66+ }
67+ } catch (e: Exception ) {
68+ log.error(" Error initializing the streaming sink:" , e)
69+ }
70+
71+ // Register required services for the Procedures
72+ StreamsSinkProcedures .registerStreamsSinkConfiguration(streamsSinkConfiguration)
73+ StreamsSinkProcedures .registerStreamsEventConsumerFactory(eventSink!! .getEventConsumerFactory())
74+ StreamsSinkProcedures .registerStreamsEventSinkConfigMapper(eventSink!! .getEventSinkConfigMapper())
75+ StreamsSinkProcedures .registerStreamsEventSink(eventSink!! )
76+ }
77+
78+ private fun runInASingleInstance () {
79+ // check if is writeable instance
80+ Neo4jUtils .executeInWriteableInstance(db) {
81+ if (streamsSinkConfiguration.clusterOnly) {
82+ log.info("""
83+ |Cannot init the Streams Sink module as is forced to work only in a cluster env,
84+ |please check the value of `streams.${StreamsSinkConfigurationConstants .CLUSTER_ONLY } `
85+ """ .trimMargin())
86+ } else {
87+ initSinkModule()
88+ }
89+ }
90+ }
91+
92+ private fun initSinkModule () {
93+ if (streamsSinkConfiguration.checkApocTimeout > - 1 ) {
94+ waitForApoc()
95+ } else {
96+ initSink()
97+ }
98+ }
99+
100+ private fun waitForApoc () {
101+ GlobalScope .launch(Dispatchers .IO ) {
102+ val success = StreamsUtils .blockUntilTrueOrTimeout(streamsSinkConfiguration.checkApocTimeout, streamsSinkConfiguration.checkApocInterval) {
103+ val hasApoc = Neo4jUtils .hasApoc(db)
104+ if (! hasApoc && log.isDebugEnabled) {
105+ log.debug(" APOC not loaded yet, next check in ${streamsSinkConfiguration.checkApocInterval} ms" )
106+ }
107+ hasApoc
108+ }
109+ if (success) {
110+ initSink()
111+ } else {
112+ log.info(" Streams Sink plugin not loaded as APOC are not installed" )
113+ }
114+ }
115+ }
116+
117+ private fun initSink () {
118+ eventSink?.start()
119+ eventSink?.printInvalidTopics()
120+ }
121+
122+ override fun unavailable () = runBlocking {
123+ mutex.withLock {
124+ setAvailable(db, false )
125+ eventSink?.stop()
126+ }
127+ Unit
128+ }
129+
130+ companion object {
131+ @JvmStatic private val available = ConcurrentHashMap <String , Boolean >()
132+
133+ fun isAvailable (db : GraphDatabaseAPI ) = available.getOrDefault(db.databaseLayout().databaseDirectory().absolutePath, false )
134+
135+ fun setAvailable (db : GraphDatabaseAPI , isAvailable : Boolean ): Unit = available.set(db.databaseLayout().databaseDirectory().absolutePath, isAvailable)
136+
137+ fun remove (db : GraphDatabaseAPI ) = available.remove(db.databaseLayout().databaseDirectory().absolutePath)
138+ }
139+ }
0 commit comments