/**
* Copyright 2015 Eediom Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.araqne.logdb.nashorn.impl;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import org.apache.felix.ipojo.annotations.Component;
import org.apache.felix.ipojo.annotations.Invalidate;
import org.apache.felix.ipojo.annotations.Provides;
import org.apache.felix.ipojo.annotations.Requires;
import org.apache.felix.ipojo.annotations.Validate;
import org.araqne.confdb.Config;
import org.araqne.confdb.ConfigDatabase;
import org.araqne.confdb.ConfigIterator;
import org.araqne.confdb.ConfigService;
import org.araqne.confdb.Predicates;
import org.araqne.logdb.cep.Event;
import org.araqne.logdb.cep.EventContextService;
import org.araqne.logdb.cep.EventKey;
import org.araqne.logdb.cep.EventSubscriber;
import org.araqne.logdb.nashorn.NashornEventScript;
import org.araqne.logdb.nashorn.NashornEventScriptRegistry;
import org.araqne.logdb.nashorn.NashornEventSubscription;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component(name = "logdb-nashorn-event-script-registry")
@Provides(specifications = { NashornEventScriptRegistry.class })
public class NashornEventScriptRegistryImpl implements NashornEventScriptRegistry, EventSubscriber {
private final Logger slog = LoggerFactory.getLogger(NashornEventScriptRegistryImpl.class);
@Requires
private ConfigService conf;
@Requires
private EventContextService eventContextService;
private BundleContext bc;
private File scriptDir;
private ScriptEngineManager factory;
private Set<NashornEventSubscription> subscriptions;
// script name to script mappings
private ConcurrentHashMap<String, NashornEventScript> scripts;
// topic to script name mappings. use script name instead of script itself
// to support one-script-to-many-topics and reload script scenario.
private ConcurrentHashMap<String, Set<String>> topicScripts;
public NashornEventScriptRegistryImpl(BundleContext bc) {
this.bc = bc;
this.factory = new ScriptEngineManager();
this.scriptDir = ScriptPaths.getPath("event_scripts");
}
@Validate
public void start() throws IOException {
subscriptions = Collections.synchronizedSet(new HashSet<NashornEventSubscription>());
scripts = new ConcurrentHashMap<String, NashornEventScript>();
topicScripts = new ConcurrentHashMap<String, Set<String>>();
loadScripts();
eventContextService.addSubscriber("*", this);
}
private void loadScripts() {
ConfigDatabase db = conf.ensureDatabase("araqne-logdb-nashorn");
ConfigIterator it = db.findAll(NashornEventSubscription.class);
for (NashornEventSubscription s : it.getDocuments(NashornEventSubscription.class)) {
try {
if (scripts.get(s.getScriptName()) == null) {
NashornEventScript script = newScript(s.getScriptName());
scripts.put(s.getScriptName(), script);
}
subscribe(s);
} catch (Throwable t) {
if (slog.isDebugEnabled()) {
File f = new File(scriptDir, s.getScriptName() + ".js");
slog.debug("araqne logdb nashorn: javascript event script [" + f.getAbsolutePath() + "] not found", t);
}
}
}
}
private void subscribe(NashornEventSubscription s) {
if (!subscriptions.add(s))
throw new IllegalStateException("duplicated event subscription, topic [" + s.getTopic() + "], script ["
+ s.getScriptName() + "]");
Set<String> handlers = Collections.synchronizedSet(new HashSet<String>());
Set<String> oldHandlers = topicScripts.putIfAbsent(s.getTopic(), handlers);
if (oldHandlers != null)
handlers = oldHandlers;
handlers.add(s.getScriptName());
slog.info("araqne logdb nashorn: subscribe topic [{}], event script [{}]", s.getTopic(), s.getScriptName());
}
private void unsubscribe(NashornEventSubscription s) {
if (!subscriptions.remove(s))
throw new IllegalStateException("event subscription not found, topic [" + s.getTopic() + "], script ["
+ s.getScriptName() + "]");
Set<String> handlers = topicScripts.get(s.getTopic());
if (handlers == null)
return;
handlers.remove(s.getScriptName());
}
@Invalidate
public void stop() {
eventContextService.removeSubscriber("*", this);
scripts.clear();
topicScripts.clear();
}
@Override
public void onEvent(Event event) {
EventKey key = event.getKey();
Set<String> scriptNames = topicScripts.get(key.getTopic());
if (scriptNames == null) {
if (slog.isDebugEnabled())
slog.debug("araqne logdb nashorn: no matching event script, topic [{}] key [{}]", key.getTopic(), key.getKey());
return;
}
for (String scriptName : scriptNames) {
try {
NashornEventScript script = scripts.get(scriptName);
if (script == null)
continue;
script.onEvent(script, event);
} catch (Throwable t) {
slog.warn("araqne logdb nashorn: event script should not throw any exception", t);
}
}
}
@Override
public List<NashornEventSubscription> getEventSubscriptions(String topicFilter) {
ArrayList<NashornEventSubscription> l = new ArrayList<NashornEventSubscription>();
for (NashornEventSubscription s : subscriptions) {
if (topicFilter != null && !topicFilter.equals(s.getTopic()))
continue;
l.add(s);
}
return l;
}
@Override
public void subscribeEvent(NashornEventSubscription subscription) {
subscribe(subscription);
ConfigDatabase db = conf.ensureDatabase("araqne-logdb-nashorn");
db.add(subscription, "araqne-logdb-nashorn", "subscribe event");
}
@Override
public void unsubscribeEvent(NashornEventSubscription subscription) {
unsubscribe(subscription);
Map<String, Object> pred = new HashMap<String, Object>();
pred.put("topic", subscription.getTopic());
pred.put("scriptName", subscription.getScriptName());
ConfigDatabase db = conf.ensureDatabase("araqne-logdb-nashorn");
Config c = db.findOne(NashornEventSubscription.class, Predicates.field(pred));
if (c != null)
db.remove(c, false, "araqne-logdb-nashorn", "subscribe event");
}
@Override
public void reloadScript(String scriptName) {
scripts.put(scriptName, newScript(scriptName));
}
@Override
public NashornEventScript newScript(String scriptName) {
ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(NashornEventScript.class.getClassLoader());
ScriptEngine nashornEngine = factory.getEngineByName("nashorn");
if (nashornEngine == null)
throw new IllegalStateException("cannot load nashorn engine for javascript " + scriptName);
nashornEngine.eval("var EventScript = Java.type(\"org.araqne.logdb.nashorn.NashornEventScript\");");
nashornEngine.eval(new FileReader(new File(scriptDir, scriptName + ".js")));
NashornEventScript script = (NashornEventScript) nashornEngine.eval("new " + scriptName + "();");
script.setBundleContext(bc);
return script;
} catch (Throwable t) {
throw new IllegalStateException("cannot instanciate event javascript: " + scriptName, t);
} finally {
Thread.currentThread().setContextClassLoader(oldLoader);
}
}
}