/*
* 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.ngsi.client.NgsiClient;
import com.orange.ngsi.model.RegisterContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Propagate the registrations to the remote broker.
*
* On registration error, the registrations are re-scheduled later.
*/
@Component
public class RemoteRegistrations {
private static Logger logger = LoggerFactory.getLogger(RemoteRegistration.class);
@Autowired
protected Configuration configuration;
@Autowired
protected NgsiClient ngsiClient;
/**
* Keep track of remote registrations.
*/
private class RemoteRegistration {
/**
* Registration ID on the remote broker
*/
String registrationId;
/**
* Keeps the context to register to remote broker for retries.
* Will be null once a successful registration is achieved.
*/
RegisterContext registerContext;
}
/**
* Map local registrationId to a RemoteRegistration
*/
private Map<String, RemoteRegistration> registrations = new ConcurrentHashMap<>();
/**
* Try propagating the registerContext to a remote broker
* @param registerContext the registerContext to send
* @param localRegistrationId the local registrationId
*/
public void registerContext(final RegisterContext registerContext, final String localRegistrationId) {
// When no remote broker is define, don't do anything.
final String remoteUrl = configuration.getRemoteUrl();
if (remoteUrl == null || remoteUrl.isEmpty()) {
return;
}
HttpHeaders httpHeaders = ngsiClient.getRequestHeaders(remoteUrl);
logger.debug("=> registerContext to remote broker {} with Content-Type {}", remoteUrl, httpHeaders.getContentType());
// If we already had a remote registration, reset its registerContext
String previousRemoteRegistrationId = resetRemoteRegistration(localRegistrationId);
// Update the registerContext with the previous remote registrationId if any
registerContext.setRegistrationId(previousRemoteRegistrationId);
configuration.addRemoteHeaders(httpHeaders);
ngsiClient.registerContext(remoteUrl, httpHeaders, registerContext).addCallback(
result -> {
String remoteRegistrationId = result.getRegistrationId();
boolean error = result.getErrorCode() != null || remoteRegistrationId == null;
if (error) {
logger.warn("failed to register {} to remote broker (will retry later) with error {}", localRegistrationId, result.getErrorCode());
} else {
logger.debug("successfully registered {} to remote broker ({})", localRegistrationId, result.getRegistrationId());
}
// On error, keep registerContext for future retry
updateRemoteRegistration(localRegistrationId, remoteRegistrationId, error ? registerContext : null);
},
ex -> {
logger.warn("failed to register {} to remote broker (will retry later) with error {}", localRegistrationId, ex.toString());
updateRemoteRegistration(localRegistrationId, null, registerContext);
});
}
/**
* Scheduled task that will retry failed remote registrations.
* For each remote registration, that still have a non null registerContext, retry the registration.
*/
@Scheduled(fixedDelay = 60000)
public void registerPendingRemoteRegistrations() {
registrations.forEach((localRegistrationId, remoteRegistration) -> {
RegisterContext registerContext = remoteRegistration.registerContext;
if (registerContext != null) {
registerContext(registerContext, localRegistrationId);
}
});
}
/**
* Find the remote registrationId corresponding to a local registrationId
* @param localRegistrationId the local registrationId
* @return the remote registrationId or null
*/
public synchronized String getRemoteRegistrationId(String localRegistrationId) {
RemoteRegistration remoteRegistration = registrations.get(localRegistrationId);
return remoteRegistration != null ? remoteRegistration.registrationId : null;
}
/**
* Remove a remote registration associated to a local registrationId
* @param localRegistrationId the local registrationId
*/
public synchronized void removeRegistration(String localRegistrationId) {
registrations.remove(localRegistrationId);
}
/**
* Reset any RemoteRegistration associated to the local registrationId (remove the registerContext)
* @param localRegistrationId the local registrationId
* @return the previous remote registrationId, or null
*/
private synchronized String resetRemoteRegistration(String localRegistrationId) {
RemoteRegistration registration = registrations.get(localRegistrationId);
if (registration != null) {
registration.registerContext = null;
return registration.registrationId;
}
return null;
}
/**
* Update the remote registration data associated to a local registration id
* @param localRegistrationId the local registrationId
* @param remoteRegistrationId the remote registrationId to update
* @param registerContext the registerContext to update
*/
private synchronized void updateRemoteRegistration(String localRegistrationId, String remoteRegistrationId, RegisterContext registerContext) {
RemoteRegistration registration = registrations.get(localRegistrationId);
if (registration == null) {
registration = new RemoteRegistration();
registrations.put(localRegistrationId, registration);
}
registration.registrationId = remoteRegistrationId;
registration.registerContext = registerContext;
}
}