/*
* 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.channel.impl.transports;
import org.kaaproject.kaa.client.channel.EventTransport;
import org.kaaproject.kaa.client.event.EventManager;
import org.kaaproject.kaa.client.persistence.KaaClientState;
import org.kaaproject.kaa.common.TransportType;
import org.kaaproject.kaa.common.endpoint.gen.Event;
import org.kaaproject.kaa.common.endpoint.gen.EventSequenceNumberRequest;
import org.kaaproject.kaa.common.endpoint.gen.EventSyncRequest;
import org.kaaproject.kaa.common.endpoint.gen.EventSyncResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
public class DefaultEventTransport extends AbstractKaaTransport implements EventTransport {
private static final Logger LOG = LoggerFactory.getLogger(DefaultEventTransport.class);
private final Map<Integer, Set<Event>> pendingEvents = new HashMap<>();
private final EventComparator eventSeqNumberComparator = new EventComparator();
private final KaaClientState clientState;
private final AtomicInteger startEventSn;
private EventManager eventManager;
private boolean isEventSnSynchronized = false;
public DefaultEventTransport(KaaClientState state) {
this.clientState = state;
this.startEventSn = new AtomicInteger(clientState.getEventSeqNum());
}
@Override
public EventSyncRequest createEventRequest(Integer requestId) {
if (eventManager != null) {
EventSyncRequest request = new EventSyncRequest();
eventManager.fillEventListenersSyncRequest(request);
if (isEventSnSynchronized) {
Set<Event> eventsSet = new HashSet<Event>();
if (!pendingEvents.isEmpty()) {
for (Map.Entry<Integer, Set<Event>> pendingEntry : pendingEvents.entrySet()) {
LOG.debug("Have not received response for {} events sent with request id {}",
pendingEntry.getValue().size(),
pendingEntry.getKey());
eventsSet.addAll(pendingEntry.getValue());
}
}
eventsSet.addAll(eventManager.pollPendingEvents());
List<Event> events = new ArrayList<Event>(eventsSet);
if (!events.isEmpty()) {
Collections.sort(events, eventSeqNumberComparator);
LOG.debug("Going to send {} event{}", events.size(),
(events.size() == 1 ? "" : "s")); // NOSONAR
request.setEvents(events);
pendingEvents.put(requestId, eventsSet);
}
request.setEventSequenceNumberRequest(null);
} else {
request.setEventSequenceNumberRequest(new EventSequenceNumberRequest());
LOG.trace("Sending event sequence number request: " + "restored_sn = {}", startEventSn);
}
return request;
}
return null;
}
@Override
public void onEventResponse(EventSyncResponse response) {
if (eventManager != null) {
if (!isEventSnSynchronized && response.getEventSequenceNumberResponse() != null) {
int lastSn = response.getEventSequenceNumberResponse().getSeqNum();
int expectedSn = lastSn > 0 ? lastSn + 1 : lastSn;
if (startEventSn.get() != expectedSn) {
startEventSn.set(expectedSn);
clientState.setEventSeqNum(startEventSn.get());
Set<Event> eventsSet = new HashSet<Event>();
for (Set<Event> events : pendingEvents.values()) {
eventsSet.addAll(events);
}
eventsSet.addAll(eventManager.peekPendingEvents());
List<Event> events = new ArrayList<Event>(eventsSet);
Collections.sort(events, eventSeqNumberComparator);
clientState.setEventSeqNum(startEventSn.get() + events.size());
if (!events.isEmpty() && events.get(0).getSeqNum() != startEventSn.get()) {
LOG.info("Put in order event sequence numbers (expected: {}, actual: {})",
startEventSn, events.get(0).getSeqNum());
for (Event e : events) {
e.setSeqNum(startEventSn.getAndIncrement());
}
} else {
startEventSn.getAndAdd(events.size());
}
LOG.info("Event sequence number is unsynchronized. Set to {}", startEventSn);
} else {
LOG.info("Event sequence number is up to date: {}", startEventSn);
}
isEventSnSynchronized = true;
}
if (response.getEvents() != null && !response.getEvents().isEmpty()) {
List<Event> events = new ArrayList<>(response.getEvents());
Collections.sort(events, eventSeqNumberComparator);
for (Event event : events) {
eventManager.onGenericEvent(event.getEventClassFQN(), event.getEventData().array(),
event.getSource());
}
}
if (response.getEventListenersResponses() != null
&& !response.getEventListenersResponses().isEmpty()) {
eventManager.eventListenersResponseReceived(response.getEventListenersResponses());
}
}
LOG.trace("Processed event response");
}
@Override
public void setEventManager(EventManager manager) {
this.eventManager = manager;
}
@Override
public void onSyncResposeIdReceived(Integer requestId) {
LOG.debug("Events sent with request id {} were accepted.", requestId);
Set<Event> acceptedEvents = pendingEvents.remove(requestId);
if (acceptedEvents != null) {
Iterator<Entry<Integer, Set<Event>>> entrySetIterator = pendingEvents.entrySet().iterator();
while (entrySetIterator.hasNext()) {
Entry<Integer, Set<Event>> entry = entrySetIterator.next();
entry.getValue().removeAll(acceptedEvents);
if (entry.getValue().isEmpty()) {
LOG.debug("Remove entry for request {}.", requestId);
entrySetIterator.remove();
}
}
}
}
@Override
protected TransportType getTransportType() {
return TransportType.EVENT;
}
@Override
public void blockEventManager() {
if (eventManager != null) {
eventManager.engageDataChannel();
}
}
@Override
public void releaseEventManager() {
if (eventManager != null) {
if (eventManager.releaseDataChannel()) {
sync();
}
}
}
class EventComparator implements Comparator<Event> {
@Override
public int compare(Event e1, Event e2) {
return e1.getSeqNum() - e2.getSeqNum();
}
}
}