/*
* Copyright (C) 2015 Orange
*
* This software is distributed under the terms and conditions of the 'GNU GENERAL PUBLIC LICENSE
* Version 2' license which can be found in the file 'LICENSE.txt' in this package distribution or
* at 'http://www.gnu.org/licenses/gpl-2.0-standalone.html'.
*/
package com.orange.cepheus.broker;
import com.orange.cepheus.broker.exception.RegistrationException;
import com.orange.cepheus.broker.exception.RegistrationPersistenceException;
import com.orange.cepheus.broker.model.Registration;
import com.orange.cepheus.broker.persistence.RegistrationsRepository;
import com.orange.ngsi.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.xml.datatype.DatatypeFactory;
import java.net.URI;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;
/**
* Maintains the list of all context registrations
*/
@Component
public class LocalRegistrations {
private static Logger logger = LoggerFactory.getLogger(LocalRegistrations.class);
/**
* All registrations updates are forwarded to the remote broker
*/
@Autowired
protected RemoteRegistrations remoteRegistrations;
@Autowired
private Patterns patterns;
@Autowired
protected RegistrationsRepository registrationsRepository;
/**
* List of all registrations
*/
Map<String, Registration> registrations = new ConcurrentHashMap<>();
/**
* Add or update a new context registration.
* When the duration of the context is set to zero, this is handled as a remove.
* @param registerContext
* @return contextRegistrationId
*/
public String updateRegistrationContext(RegisterContext registerContext) throws RegistrationException, RegistrationPersistenceException {
Duration duration = registrationDuration(registerContext);
String registrationId = registerContext.getRegistrationId();
// Handle a zero duration as a special remove operation
if (duration.isZero() && registrationId != null) {
registrationsRepository.removeRegistration(registrationId);
registrations.remove(registrationId);
remoteRegistrations.removeRegistration(registrationId);
return registrationId;
}
// Compile all entity patterns now to check for conformance (result is cached for later use)
try {
registerContext.getContextRegistrationList().forEach(c -> c.getEntityIdList().forEach(patterns::getPattern));
} catch (PatternSyntaxException e) {
throw new RegistrationException("bad pattern", e);
}
// Generate a registration id if none was provided or if it does not refer to an existing registration
if (registrationId == null || registrations.get(registrationId) == null) {
registrationId = UUID.randomUUID().toString();
registerContext.setRegistrationId(registrationId);
}
// Exists in database
Instant expirationDate = Instant.now().plus(duration);
Registration registration;
//TODO: instead of use insert or update, use replace instruction of sqlite
try {
registration = registrationsRepository.getRegistration(registerContext.getRegistrationId());
// update registration
registration.setExpirationDate(expirationDate);
registration.setRegisterContext(registerContext);
registrationsRepository.updateRegistration(registration);
} catch (EmptyResultDataAccessException e) {
// Create registration and set the expiration date
registration = new Registration(expirationDate, registerContext);
registrationsRepository.saveRegistration(registration);
}
registrations.put(registrationId, registration);
// Forward to remote broker
remoteRegistrations.registerContext(registerContext, registrationId);
return registrationId;
}
/**
* Retrieve a registration (warning: might be expired !)
* @param registrationId the id of the registration
* @return the corresponding registration or null if not found
*/
public Registration getRegistration(String registrationId) {
return registrations.get(registrationId);
}
/**
* Find the providingApplication of a context element
* @param searchEntityId the entity id to search
* @param searchAttributes the attributes to search
* @return list of matching providing applications
*/
public Iterator<URI> findProvidingApplication(EntityId searchEntityId, Set<String> searchAttributes) {
// Filter out expired registrations
Predicate<Registration> filterExpired = registration -> registration.getExpirationDate().isAfter(Instant.now());
// Filter only matching entity ids
Predicate<EntityId> filterEntityId = patterns.getFilterEntityId(searchEntityId);
// Only filter by attributes if search is looking for them
final boolean noAttributes = searchAttributes == null || searchAttributes.size() == 0;
// Filter each registration (remove expired) and return its providing application
// if at least one of its listed entities matches the searched context element
// and if all searched attributes are defined in the registration (if any)
return registrations.values().stream()
.filter(filterExpired).map(registration -> registration.getRegisterContext().getContextRegistrationList())
.flatMap(List::stream)
.filter(c -> c.getEntityIdList().stream().filter(filterEntityId).findFirst().isPresent()
&& (noAttributes || allContextRegistrationAttributes(c).containsAll(searchAttributes)))
.map(ContextRegistration::getProvidingApplication).iterator();
}
/**
* @return all the names of the attributes of a context registration
*/
private Collection<String> allContextRegistrationAttributes(ContextRegistration contextRegistration) {
return contextRegistration.getContextRegistrationAttributeList().stream().map(ContextRegistrationAttribute::getName).collect(Collectors.toList());
}
/**
* Removed expired registrations every min
*/
@Scheduled(fixedDelay = 60000)
public void purgeExpiredContextRegistrations() {
final Instant now = Instant.now();
registrations.forEach((registrationId, registration) -> {
if (registration.getExpirationDate().isBefore(now)) {
registrations.remove(registrationId);
remoteRegistrations.removeRegistration(registrationId);
try {
registrationsRepository.removeRegistration(registrationId);
} catch (RegistrationPersistenceException e) {
logger.error("Failed to remove registration from database", e);
}
}
});
}
/**
* @return the duration of the registration
* @throws RegistrationException
*/
private Duration registrationDuration(RegisterContext registerContext) throws RegistrationException {
// Use java.xml.datatype functions as java.time do not handle durations with months and years...
try {
long duration = DatatypeFactory.newInstance().newDuration(registerContext.getDuration()).getTimeInMillis(new Date());
return Duration.ofMillis(duration);
} catch (Exception e) {
throw new RegistrationException("bad duration: "+registerContext.getDuration(), e);
}
}
}