/*
* Copyright 2014-2016 CyberVision, 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.kaaproject.kaa.client.event;
import org.kaaproject.kaa.client.channel.EventTransport;
import org.kaaproject.kaa.client.context.ExecutorContext;
import org.kaaproject.kaa.client.persistence.KaaClientState;
import org.kaaproject.kaa.client.transact.TransactionId;
import org.kaaproject.kaa.common.endpoint.gen.Event;
import org.kaaproject.kaa.common.endpoint.gen.EventListenersRequest;
import org.kaaproject.kaa.common.endpoint.gen.EventListenersResponse;
import org.kaaproject.kaa.common.endpoint.gen.EventSyncRequest;
import org.kaaproject.kaa.common.endpoint.gen.SyncResponseResultType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
/**
* Default {@link EventManager} implementation.
*
* @author Taras Lemkin
*/
public class DefaultEventManager implements EventManager {
private static final Logger LOG = LoggerFactory.getLogger(DefaultEventManager.class);
private final Set<EventFamily> registeredEventFamilies = new HashSet<EventFamily>();
private final List<Event> currentEvents = new LinkedList<Event>();
private final Object eventsGuard = new Object();
private final Object trxGuard = new Object();
private final Map<Integer, EventListenersRequestBinding> eventListenersRequests =
new HashMap<Integer, EventListenersRequestBinding>();
private final EventTransport transport;
private final KaaClientState state;
private final ExecutorContext executorContext;
private final Map<TransactionId, List<Event>> transactions = new HashMap<>();
private Boolean isEngaged = false;
/**
* All-args constructor.
*/
public DefaultEventManager(KaaClientState state, ExecutorContext executorContext,
EventTransport transport) {
this.state = state;
this.transport = transport;
this.executorContext = executorContext;
}
@Override
public void fillEventListenersSyncRequest(EventSyncRequest request) {
if (!eventListenersRequests.isEmpty()) {
LOG.debug("There are {} unresolved eventListenersResolution request{}",
eventListenersRequests.size(),
(eventListenersRequests.size() == 1 ? "" : "s")); // NOSONAR
List<EventListenersRequest> requests = new ArrayList<EventListenersRequest>();
for (Map.Entry<Integer, EventListenersRequestBinding> entry : eventListenersRequests
.entrySet()) {
if (!entry.getValue().isSent()) {
requests.add(entry.getValue().getRequest());
entry.getValue().setSent(Boolean.TRUE);
}
}
request.setEventListenersRequests(requests);
}
}
@Override
public void clearState() {
synchronized (eventsGuard) {
currentEvents.clear();
}
}
@Override
public void produceEvent(String eventFqn, byte[] data, String target) {
produceEvent(eventFqn, data, target, null);
}
@Override
public void produceEvent(String eventFqn, byte[] data, String target, TransactionId trxId) {
if (trxId == null) {
LOG.info("Producing event [eventClassFQN: {}, target: {}]", eventFqn,
(target != null ? target : "broadcast")); // NOSONAR
synchronized (eventsGuard) {
currentEvents.add(new Event(state.getAndIncrementEventSeqNum(), eventFqn,
ByteBuffer.wrap(data), null, target));
}
if (!isEngaged) {
transport.sync();
}
} else {
LOG.info("Adding event [eventClassFQN: {}, target: {}] to transaction {}",
eventFqn, target != null ? target : "broadcast", trxId); // NOSONAR
synchronized (trxGuard) {
List<Event> events = transactions.get(trxId);
if (events != null) {
events.add(new Event(-1, eventFqn, ByteBuffer.wrap(data), null, target));
} else {
LOG.warn("Transaction with id {} is missing. Ignoring event");
}
}
}
}
@Override
public void registerEventFamily(EventFamily eventFamily) {
registeredEventFamilies.add(eventFamily);
}
@Override
public void onGenericEvent(final String eventFqn, final byte[] data, final String source) {
LOG.info("Received event [eventClassFQN: {}]", eventFqn);
for (final EventFamily family : registeredEventFamilies) {
LOG.info("Lookup event fqn {} in family {}", eventFqn, family);
if (family.getSupportedEventFqns().contains(eventFqn)) {
LOG.info("Event fqn {} found in family {}", eventFqn, family);
executorContext.getCallbackExecutor().submit(new Runnable() {
@Override
public void run() {
family.onGenericEvent(eventFqn, data, source);
}
});
}
}
}
@Override
public int findEventListeners(List<String> eventFqns, FindEventListenersCallback listener) {
int requestId = new Random().nextInt();
EventListenersRequest request = new EventListenersRequest(requestId, eventFqns);
EventListenersRequestBinding bind = new EventListenersRequestBinding(listener, request);
eventListenersRequests.put(requestId, bind);
LOG.debug("Adding event listener resolution request. Request ID: {}", requestId);
if (!isEngaged) {
transport.sync();
}
return requestId;
}
@Override
public void eventListenersResponseReceived(List<EventListenersResponse> response) {
for (final EventListenersResponse singleResponse : response) {
LOG.debug("Received event listener resolution response: {}", response);
final EventListenersRequestBinding bind = eventListenersRequests.remove(
singleResponse.getRequestId());
executorContext.getCallbackExecutor().submit(new Runnable() {
@Override
public void run() {
if (bind != null) {
if (singleResponse.getResult() == SyncResponseResultType.SUCCESS) {
bind.getListener().onEventListenersReceived(singleResponse.getListeners());
} else {
bind.getListener().onRequestFailed();
}
}
}
});
}
}
@Override
public List<Event> pollPendingEvents() {
return getPendingEvents(true);
}
@Override
public List<Event> peekPendingEvents() {
return getPendingEvents(false);
}
private List<Event> getPendingEvents(boolean clear) {
List<Event> pendingEvents = new ArrayList<Event>();
synchronized (eventsGuard) {
pendingEvents.addAll(currentEvents);
if (clear) {
currentEvents.clear();
}
}
return pendingEvents;
}
@Override
public TransactionId beginTransaction() {
TransactionId trxId = new TransactionId();
synchronized (trxGuard) {
if (!transactions.containsKey(trxId)) {
LOG.debug("Creating events transaction with id {}", trxId);
transactions.put(trxId, new LinkedList<Event>());
}
}
return trxId;
}
@Override
public void commit(TransactionId trxId) {
LOG.debug("Commiting events transaction with id {}", trxId);
synchronized (trxGuard) {
List<Event> eventsToCommit = transactions.remove(trxId);
synchronized (eventsGuard) {
for (Event e : eventsToCommit) {
e.setSeqNum(state.getAndIncrementEventSeqNum());
currentEvents.add(e);
}
}
if (!isEngaged) {
transport.sync();
}
}
}
@Override
public void rollback(TransactionId trxId) {
LOG.debug("Rolling back events transaction with id {}", trxId);
synchronized (trxGuard) {
List<Event> eventsToRemove = transactions.remove(trxId);
if (eventsToRemove != null) {
for (Event e : eventsToRemove) {
LOG.trace("Removing event {}", e);
}
} else {
LOG.debug("Transaction with id {} was not created", trxId);
}
}
}
@Override
public synchronized void engageDataChannel() {
isEngaged = true;
}
@Override
public synchronized boolean releaseDataChannel() {
isEngaged = false;
boolean needSync = !currentEvents.isEmpty();
if (!needSync) {
for (EventListenersRequestBinding b : eventListenersRequests.values()) {
needSync |= !b.isSent();
}
}
return needSync;
}
private class EventListenersRequestBinding {
private final FindEventListenersCallback listener;
private final EventListenersRequest request;
private Boolean sent;
public EventListenersRequestBinding(
FindEventListenersCallback listener, EventListenersRequest request) {
this.listener = listener;
this.request = request;
this.sent = false;
}
public FindEventListenersCallback getListener() {
return listener;
}
public EventListenersRequest getRequest() {
return request;
}
public Boolean isSent() {
return sent;
}
public void setSent(Boolean sent) {
this.sent = sent;
}
}
}