/*
* Copyright 2014 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.groovy.impl;
import groovy.util.GroovyScriptEngine;
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 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.groovy.GroovyEventScript;
import org.araqne.logdb.groovy.GroovyEventScriptRegistry;
import org.araqne.logdb.groovy.GroovyEventSubscription;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component(name = "logdb-groovy-event-script-registry")
@Provides(specifications = { GroovyEventScriptRegistry.class })
public class GroovyEventScriptRegistryImpl implements GroovyEventScriptRegistry, EventSubscriber {
private final Logger slog = LoggerFactory.getLogger(GroovyEventScriptRegistryImpl.class);
@Requires
private ConfigService conf;
@Requires
private EventContextService eventContextService;
private BundleContext bc;
private GroovyScriptEngine gse;
private Set<GroovyEventSubscription> subscriptions;
// script name to script mappings
private ConcurrentHashMap<String, GroovyEventScript> 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 GroovyEventScriptRegistryImpl(BundleContext bc) {
this.bc = bc;
}
@Validate
public void start() throws IOException {
subscriptions = Collections.synchronizedSet(new HashSet<GroovyEventSubscription>());
scripts = new ConcurrentHashMap<String, GroovyEventScript>();
topicScripts = new ConcurrentHashMap<String, Set<String>>();
String path = ScriptPaths.getPath("event_scripts");
gse = new GroovyScriptEngine(path);
loadScripts();
eventContextService.addSubscriber("*", this);
}
private void loadScripts() {
ConfigDatabase db = conf.ensureDatabase("araqne-logdb-groovy");
ConfigIterator it = db.findAll(GroovyEventSubscription.class);
for (GroovyEventSubscription s : it.getDocuments(GroovyEventSubscription.class)) {
if (scripts.get(s.getScriptName()) == null) {
GroovyEventScript script = newScript(s.getScriptName());
scripts.put(s.getScriptName(), script);
}
subscribe(s);
}
}
private void subscribe(GroovyEventSubscription 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 groovy: subscribe topic [{}], event script [{}]", s.getTopic(), s.getScriptName());
}
private void unsubscribe(GroovyEventSubscription 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);
if (gse != null)
gse.getGroovyClassLoader().clearCache();
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 groovy: no matching event script, topic [{}] key [{}]", key.getTopic(), key.getKey());
return;
}
for (String scriptName : scriptNames) {
try {
GroovyEventScript script = scripts.get(scriptName);
if (script == null)
continue;
script.onEvent(event);
} catch (Throwable t) {
slog.warn("araqne logdb groovy: event script should not throw any exception", t);
}
}
}
@Override
public List<GroovyEventSubscription> getEventSubscriptions(String topicFilter) {
ArrayList<GroovyEventSubscription> l = new ArrayList<GroovyEventSubscription>();
for (GroovyEventSubscription s : subscriptions) {
if (topicFilter != null && !topicFilter.equals(s.getTopic()))
continue;
l.add(s);
}
return l;
}
@Override
public void subscribeEvent(GroovyEventSubscription subscription) {
subscribe(subscription);
ConfigDatabase db = conf.ensureDatabase("araqne-logdb-groovy");
db.add(subscription, "araqne-logdb-groovy", "subscribe event");
}
@Override
public void unsubscribeEvent(GroovyEventSubscription 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-groovy");
Config c = db.findOne(GroovyEventSubscription.class, Predicates.field(pred));
if (c != null)
db.remove(c, false, "araqne-logdb-groovy", "subscribe event");
}
@Override
public void reloadScript(String scriptName) {
scripts.put(scriptName, newScript(scriptName));
}
@Override
public GroovyEventScript newScript(String scriptName) {
try {
Class<?> clazz = gse.loadScriptByName(scriptName + ".groovy");
Object o = clazz.newInstance();
GroovyEventScript script = (GroovyEventScript) o;
script.setBundleContext(bc);
return script;
} catch (Throwable t) {
throw new IllegalStateException("cannot instanciate groovy event script: " + scriptName, t);
}
}
}