/*
* Copyright 2012-2014 Nikolay A. Viguro
* <p/>
* 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
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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 ru.iris.events;
import com.avaje.ebean.Ebean;
import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import ru.iris.common.database.model.Event;
import ru.iris.common.messaging.JsonEnvelope;
import ru.iris.common.messaging.JsonMessaging;
import ru.iris.common.messaging.JsonNotification;
import ru.iris.common.messaging.model.command.CommandAdvertisement;
import ru.iris.common.messaging.model.events.*;
import ru.iris.common.modulestatus.Status;
import javax.script.*;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* Author: Nikolay A. Viguro
* Date: 19.11.13
* Time: 11:25
*/
public class EventsService
{
private final Logger LOGGER = LogManager.getLogger(EventsService.class.getName());
private final Compilable engine = (Compilable) new ScriptEngineManager().getEngineByName("nashorn");
private Map<String, CompiledScript> compiledScriptMap;
private Map<String, CompiledScript> compiledCommandScriptMap = new HashMap<>();
private final JsonMessaging jsonMessaging = new JsonMessaging(UUID.randomUUID(), "events");
private final Logger scriptLogger = LogManager.getLogger(EventsService.class.getName());
private List<Event> events;
public EventsService()
{
Status status = new Status("Events");
if (status.checkExist()) {
status.running();
} else {
status.addIntoDB("Events", "Service that listen for events and exec scripts as needed");
}
try {
events = Ebean.find(Event.class).findList();
// take pause to save/remove new entity
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// load all scripts, compile and put into map
compiledScriptMap = loadAndCompile(events);
// Pass jsonmessaging instance to js engine
Bindings bindings = new SimpleBindings();
bindings.put("jsonMessaging", jsonMessaging);
bindings.put("LOGGER", scriptLogger);
// subscribe to events from db
for (Event event : events) {
jsonMessaging.subscribe(event.getSubject());
LOGGER.debug("Subscribe to subject: " + event.getSubject());
}
// command launch
jsonMessaging.subscribe("event.command");
// scripts
jsonMessaging.subscribe("event.script.get");
jsonMessaging.subscribe("event.script.save");
jsonMessaging.subscribe("event.script.delete");
jsonMessaging.subscribe("event.script.list");
jsonMessaging.subscribe("event.reload");
jsonMessaging.setNotification(new JsonNotification() {
@Override
public void onNotification(JsonEnvelope envelope) {
LOGGER.debug("Got envelope with subject: " + envelope.getSubject());
try {
// Get script content
if (envelope.getObject() instanceof EventGetScriptAdvertisement) {
LOGGER.debug("Return JS script to: " + envelope.getReceiverInstance());
EventGetScriptAdvertisement advertisement = envelope.getObject();
File jsFile;
if (advertisement.isCommand())
jsFile = new File("./scripts/command/" + advertisement.getName());
else
jsFile = new File("./scripts/" + advertisement.getName());
jsonMessaging.response(envelope, new EventResponseGetScriptAdvertisement(FileUtils.readFileToString(jsFile)));
}
// Save new/existing script
else if (envelope.getObject() instanceof EventResponseSaveScriptAdvertisement) {
EventResponseSaveScriptAdvertisement advertisement = envelope.getObject();
LOGGER.debug("Request to save changes: " + advertisement.getName());
File jsFile;
if (advertisement.isCommand())
jsFile = new File("./scripts/command/" + advertisement.getName());
else
jsFile = new File("./scripts/" + advertisement.getName());
FileUtils.writeStringToFile(jsFile, advertisement.getBody());
LOGGER.info("Restart event service (reason: script change)");
reloadService();
}
// Remove script
else if (envelope.getObject() instanceof EventRemoveScriptAdvertisement) {
EventRemoveScriptAdvertisement advertisement = envelope.getObject();
LOGGER.debug("Request to remove script: " + advertisement.getName());
File jsFile;
if (advertisement.isCommand())
jsFile = new File("./scripts/command/" + advertisement.getName());
else
jsFile = new File("./scripts/" + advertisement.getName());
FileUtils.forceDelete(jsFile);
LOGGER.info("Restart event service (reason: script removed)");
reloadService();
}
// List available scripts
else if (envelope.getObject() instanceof EventListScriptsAdvertisement) {
EventListScriptsAdvertisement advertisement = envelope.getObject();
File jsFile;
if (advertisement.isCommand())
jsFile = new File("./scripts/command/");
else
jsFile = new File("./scripts/");
EventResponseListScriptsAdvertisement response = new EventResponseListScriptsAdvertisement();
response.setScripts((List<File>) FileUtils.listFiles(jsFile, new String[]{"js"}, false));
jsonMessaging.response(envelope, response);
}
// Check command and launch script
else if (envelope.getObject() instanceof CommandAdvertisement) {
CommandAdvertisement advertisement = envelope.getObject();
bindings.put("advertisement", envelope.getObject());
if (compiledCommandScriptMap.get(advertisement.getScript()) == null) {
LOGGER.debug("Compile command script: " + advertisement.getScript());
File jsFile = new File("./scripts/command/" + advertisement.getScript());
CompiledScript compile = engine.compile(FileUtils.readFileToString(jsFile));
compiledCommandScriptMap.put(advertisement.getScript(), compile);
LOGGER.debug("Launch compiled command script: " + advertisement.getScript());
compile.eval(bindings);
} else {
LOGGER.info("Launch compiled command script: " + advertisement.getScript());
compiledCommandScriptMap.get(advertisement.getScript()).eval(bindings);
}
} else if (envelope.getObject() instanceof EventChangesAdvertisement) {
reloadService();
} else {
for (Event event : events) {
if (envelope.getSubject().equals(event.getSubject()) || wildCardMatch(event.getSubject(), envelope.getSubject())) {
LOGGER.debug("Run compiled script: " + event.getScript());
try {
bindings.put("advertisement", envelope.getObject());
CompiledScript script = compiledScriptMap.get(event.getScript());
if (script != null)
script.eval(bindings);
else
LOGGER.error("Error! Script " + event.getScript() + " is NULL!");
} catch (ScriptException e) {
LOGGER.error("Error in script scripts/command/" + event.getScript() + ": " + e.toString());
e.printStackTrace();
}
}
}
}
} catch (ScriptException | IOException e) {
LOGGER.error("Error in script: " + e.toString());
e.printStackTrace();
}
}
});
jsonMessaging.start();
} catch (final RuntimeException t) {
LOGGER.error("Error in Events!");
status.crashed();
t.printStackTrace();
}
}
private void reloadService() throws RuntimeException {
LOGGER.info("Reload event service");
// unsubscribe current events
for (Event event : events) {
jsonMessaging.unsubscribe(event.getSubject());
LOGGER.debug("Unsubscribe from subject: " + event.getSubject());
}
events = Ebean.find(Event.class).findList();
// subscribe to events from db
for (Event event : events) {
jsonMessaging.subscribe(event.getSubject());
LOGGER.debug("Subscribe from subject: " + event.getSubject());
}
// take pause to save/remove new entity
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// load all scripts, compile and put into map
compiledScriptMap = loadAndCompile(events);
LOGGER.info("Reload event service done");
}
private Map<String, CompiledScript> loadAndCompile(List<Event> events) throws RuntimeException {
Map<String, CompiledScript> compiledScriptMap = new HashMap<>();
for (Event event : events) {
File jsFile = new File("./scripts/" + event.getScript());
CompiledScript compile = null;
try {
compile = engine.compile(FileUtils.readFileToString(jsFile));
} catch (ScriptException | IOException e) {
LOGGER.error("Compile error: " + e.getMessage());
}
compiledScriptMap.put(event.getScript(), compile);
}
return compiledScriptMap;
}
private boolean wildCardMatch(String pattern, String text) throws RuntimeException
{
// add sentinel so don't need to worry about *'s at end of pattern
text += '\0';
pattern += '\0';
int N = pattern.length();
boolean[] states = new boolean[N + 1];
boolean[] old = new boolean[N + 1];
old[0] = true;
for (int i = 0; i < text.length(); i++)
{
char c = text.charAt(i);
states = new boolean[N + 1]; // initialized to false
for (int j = 0; j < N; j++)
{
char p = pattern.charAt(j);
// hack to handle *'s that match 0 characters
if (old[j] && (p == '*'))
{
old[j + 1] = true;
}
if (old[j] && (p == c))
{
states[j + 1] = true;
}
if (old[j] && (p == '*'))
{
states[j] = true;
}
if (old[j] && (p == '*'))
{
states[j + 1] = true;
}
}
old = states;
}
return states[N];
}
public void stop() {
jsonMessaging.close();
}
}