/*
* 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.cep.engine;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.felix.ipojo.annotations.Component;
import org.apache.felix.ipojo.annotations.Invalidate;
import org.apache.felix.ipojo.annotations.Requires;
import org.apache.felix.ipojo.annotations.Validate;
import org.araqne.cron.AbstractTickTimer;
import org.araqne.cron.TickService;
import org.araqne.logdb.cep.Event;
import org.araqne.logdb.cep.EventCause;
import org.araqne.logdb.cep.EventClock;
import org.araqne.logdb.cep.EventClockCallback;
import org.araqne.logdb.cep.EventClockItem;
import org.araqne.logdb.cep.EventContext;
import org.araqne.logdb.cep.EventContextListener;
import org.araqne.logdb.cep.EventContextService;
import org.araqne.logdb.cep.EventContextStorage;
import org.araqne.logdb.cep.EventKey;
import org.araqne.logdb.cep.EventSubscriber;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component(name = "mem-event-ctx-storage")
public class MemoryEventContextStorage implements EventContextStorage, EventContextListener {
private static final int INITIAL_CAPACITY = 11;
private final Logger slog = LoggerFactory.getLogger(MemoryEventContextStorage.class);
@Requires
private EventContextService eventContextService;
@Requires
private TickService tickService;
private ConcurrentHashMap<EventKey, EventContext> contexts;
// topic to subscribers
private ConcurrentHashMap<String, CopyOnWriteArraySet<EventSubscriber>> subscribers;
// log tick host to aging context mappings
private ConcurrentHashMap<String, EventClock<EventContext>> logClocks;
private EventClock<EventContext> realClock;
private RealClockTask realClockTask = new RealClockTask();
@Override
public String getName() {
return "mem";
}
@Validate
public void start() {
contexts = new ConcurrentHashMap<EventKey, EventContext>();
subscribers = new ConcurrentHashMap<String, CopyOnWriteArraySet<EventSubscriber>>();
logClocks = new ConcurrentHashMap<String, EventClock<EventContext>>();
realClock = new EventClock<EventContext>(new MemEventClockCallback(), "real", System.currentTimeMillis(), 10000);
tickService.addTimer(realClockTask);
eventContextService.registerStorage(this);
}
@Invalidate
public void stop() {
if (eventContextService != null) {
eventContextService.unregisterStorage(this);
}
tickService.removeTimer(realClockTask);
contexts.clear();
subscribers.clear();
}
@Override
public Set<String> getHosts() {
return new HashSet<String>(logClocks.keySet());
}
@Override
public EventClock<EventContext> getClock(String host) {
return logClocks.get(host);
}
@Override
public Iterator<EventKey> getContextKeys() {
return new HashSet<EventKey>(contexts.keySet()).iterator();
}
@Override
public Iterator<EventKey> getContextKeys(String topic) {
if (topic == null)
return new HashSet<EventKey>(contexts.keySet()).iterator();
HashSet<EventKey> keys = new HashSet<EventKey>();
for (EventKey key : contexts.keySet()) {
if (key.getTopic().equals(topic))
keys.add(key);
}
return keys.iterator();
}
@Override
public EventContext getContext(EventKey key) {
return contexts.get(key);
}
private EventClock<EventContext> ensureClock(ConcurrentHashMap<String, EventClock<EventContext>> clocks, String host,
long time) {
EventClock<EventContext> clock = null;
clock = clocks.get(host);
if (clock == null) {
clock = new EventClock<EventContext>(new MemEventClockCallback(), host, time, INITIAL_CAPACITY);
EventClock<EventContext> old = clocks.putIfAbsent(host, clock);
if (old != null)
return old;
return clock;
} else {
return clock;
}
}
@Override
public void storeContext(EventContext ctx) {
EventContext oldCtx = contexts.putIfAbsent(ctx.getKey(), ctx);
if (oldCtx == null) {
ctx.getListeners().add(this);
if (ctx.getHost() != null) {
EventClock<EventContext> logClock = ensureClock(logClocks, ctx.getHost(), ctx.getCreated());
logClock.add(ctx);
} else {
realClock.add(ctx);
}
generateEvent(ctx, EventCause.CREATE);
} else {
if (ctx.getRows().size() > 1)
oldCtx.addRow(ctx.getRows().get(0));
oldCtx.getCounter().incrementAndGet();
oldCtx.setTimeoutTime(ctx.getTimeoutTime());
}
}
@Override
public void addContextVariable(EventKey evtKey, String key, Object value) {
EventContext ctx = getContext(evtKey);
if (ctx == null)
return;
ctx.setVariable(key, value);
}
@Override
public void storeContexts(List<EventContext> contexts) {
for (EventContext context : contexts)
storeContext(context);
}
@Override
public void removeContext(EventKey key, EventCause cause) {
EventContext ctx = getContext(key);
if (contexts.remove(key, ctx)) {
ctx.getListeners().remove(this);
if (key.getHost() != null) {
EventClock<EventContext> logClock = ensureClock(logClocks, ctx.getHost(), ctx.getCreated());
logClock.remove(ctx);
} else {
realClock.remove(ctx);
}
generateEvent(ctx, cause);
}
}
@Override
public void advanceTime(String host, long logTime) {
EventClock<EventContext> logClock = ensureClock(logClocks, host, logTime);
logClock.setTime(logTime, false);
}
@Override
public void clearClocks() {
contexts.clear();
realClock = new EventClock<EventContext>(new MemEventClockCallback(), "real", System.currentTimeMillis(), 10000);
logClocks = new ConcurrentHashMap<String, EventClock<EventContext>>();
}
@Override
public void clearContexts() {
clearContexts(null);
}
@Override
public void clearContexts(String topic) {
Iterator<EventKey> itr = getContextKeys();
while (itr.hasNext()) {
EventKey key = itr.next();
if (topic == null || key.getTopic().equals(topic)) {
removeContext(key, EventCause.REMOVAL);
}
}
}
@Override
public void addSubscriber(String topic, EventSubscriber subscriber) {
CopyOnWriteArraySet<EventSubscriber> s = new CopyOnWriteArraySet<EventSubscriber>();
CopyOnWriteArraySet<EventSubscriber> old = subscribers.putIfAbsent(topic, s);
if (old != null)
s = old;
s.add(subscriber);
}
@Override
public void removeSubscriber(String topic, EventSubscriber subscriber) {
CopyOnWriteArraySet<EventSubscriber> s = subscribers.get(topic);
if (s != null)
s.remove(subscriber);
}
private class RealClockTask extends AbstractTickTimer {
private AtomicBoolean running = new AtomicBoolean();
@Override
public int getInterval() {
return 100;
}
@Override
public void onTick() {
if (running.compareAndSet(false, true)) {
try {
realClock.setTime(System.currentTimeMillis(), false);
} finally {
running.set(false);
}
}
}
}
private void generateEvent(EventContext ctx, EventCause cause) {
if (slog.isDebugEnabled())
slog.debug("araqne logdb cep: generate event ctx [{}] cause [{}]", ctx.getKey(), cause);
Event ev = new Event(ctx, cause);
ev.getRows().addAll(ctx.getRows());
CopyOnWriteArraySet<EventSubscriber> s = subscribers.get(ctx.getKey().getTopic());
if (s != null) {
for (EventSubscriber subscriber : s) {
try {
subscriber.onEvent(ev);
} catch (Throwable t) {
slog.error("araqne logdb cep: subscriber should not throw any exception", t);
}
}
}
// for wild subscriber
s = subscribers.get("*");
if (s != null) {
for (EventSubscriber subscriber : s) {
try {
subscriber.onEvent(ev);
} catch (Throwable t) {
slog.error("araqne logdb cep: subscriber should not throw any exception", t);
}
}
}
}
@Override
public void onUpdateTimeout(EventContext ctx) {
// update real clock queue
if (ctx.getHost() == null) {
realClock.updateTimeout(ctx);
return;
}
// update per-host case only
EventClock<EventContext> clock = ensureClock(logClocks, ctx.getHost(), ctx.getCreated());
if (clock == null)
return;
clock.updateTimeout(ctx);
}
@Override
public void removeContexts(List<EventKey> contexts, EventCause removal) {
for (EventKey key : contexts) {
removeContext(key, removal);
}
}
@Override
public List<EventContext> getContexts(Set<EventKey> keys) {
List<EventContext> contexts = new ArrayList<EventContext>();
for (EventKey key : keys) {
contexts.add(getContext(key));
}
return contexts;
}
private class MemEventClockCallback implements EventClockCallback {
@Override
public void onRemove(EventClockItem value, EventCause expire) {
removeContext(value.getKey(), expire);
}
}
}