/* * 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.controller; import com.orange.cepheus.broker.Configuration; import com.orange.cepheus.broker.LocalRegistrations; import com.orange.cepheus.broker.Subscriptions; import com.orange.cepheus.broker.exception.*; import com.orange.cepheus.broker.model.Subscription; import com.orange.ngsi.client.NgsiClient; import com.orange.ngsi.model.*; import com.orange.ngsi.server.NgsiBaseController; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import java.net.URI; import java.net.URISyntaxException; import java.util.*; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; /** * NGSI standard operations * - all NGSI-10 operations except /updateContextSubscription * - none of NGSI-9 operations but /registerContext (used by IoTAgents) */ @RestController @RequestMapping(value = {"/v1", "/v1/registry", "/ngsi9", "/NGSI9", "/ngsi10", "/NGSI10"}) public class NgsiController extends NgsiBaseController { private static Logger logger = LoggerFactory.getLogger(NgsiController.class); @Autowired LocalRegistrations localRegistrations; @Autowired Subscriptions subscriptions; @Autowired NgsiClient ngsiClient; @Autowired Configuration configuration; @Override public RegisterContextResponse registerContext(final RegisterContext register) throws RegistrationException, RegistrationPersistenceException { logger.debug("<= registerContext with id:{} duration:{}", register.getRegistrationId(), register.getDuration()); RegisterContextResponse registerContextLocalResponse = new RegisterContextResponse(); //register new registration or update previous registration (if registrationId != null) or remove registration (if duration = 0) registerContextLocalResponse.setRegistrationId(localRegistrations.updateRegistrationContext(register)); return registerContextLocalResponse; } @Override public UpdateContextResponse updateContext(final UpdateContext update) throws ExecutionException, InterruptedException, URISyntaxException { //TODO : search providingApplication for all contextElement of updateContext ContextElement contextElement = update.getContextElements().get(0); Set<String> attributesName = contextElement.getContextAttributeList().stream().map(ContextAttribute::getName).collect(Collectors.toSet()); logger.debug("<= updateContext with entityId: {} and attributes: {} ", contextElement.getEntityId().toString(), attributesName); /* * If a registration matches the updateContext, the updateContext is forwarded to the corresponding providingURL. * Else, the update is forwarded to the remote broker and the subscribers are notified. */ // Search registrations to forward updateContext Iterator<URI> providingApplication = localRegistrations.findProvidingApplication(contextElement.getEntityId(), attributesName); if (providingApplication.hasNext()) { // Forward the update to the first providing Application (command) final String providerUrl = providingApplication.next().toString(); HttpHeaders httpHeaders = ngsiClient.getRequestHeaders(providerUrl); logger.debug("=> updateContext forwarded to {} with Content-Type {}", providerUrl, httpHeaders.getContentType()); return ngsiClient.updateContext(providerUrl, httpHeaders, update).get(); } // Forward the update to the remote broker if (configuration.isRemoteForwardUpdateContext()) { final String brokerUrl = configuration.getRemoteUrl(); if (brokerUrl == null || brokerUrl.isEmpty()) { logger.warn("No remote.url parameter defined to forward updateContext"); } else { HttpHeaders httpHeaders = getRemoteBrokerHeaders(brokerUrl); logger.debug("=> updateContext forwarded to remote broker {} with Content-Type {}", brokerUrl, httpHeaders.getContentType()); ngsiClient.updateContext(brokerUrl, httpHeaders, update) .addCallback(updateContextResponse -> logUpdateContextResponse(updateContextResponse, brokerUrl), throwable -> logger.warn("UpdateContext failed for {}: {}", brokerUrl, throwable.getMessage(), throwable)); } } List<ContextElementResponse> contextElementResponseList = new ArrayList<>(); StatusCode statusCode = new StatusCode(CodeEnum.CODE_200); for (ContextElement c : update.getContextElements()) { contextElementResponseList.add(new ContextElementResponse(c, statusCode)); } String originator = configuration.getLocalUrl(); if (originator == null || originator.isEmpty()) { logger.warn("No local.url parameter defined to use as originator for sending notifyContext"); } else { // Send notifications to matching subscriptions Iterator<Subscription> matchingSubscriptions = subscriptions.findSubscriptions(contextElement.getEntityId(), attributesName); while (matchingSubscriptions.hasNext()) { Subscription subscription = matchingSubscriptions.next(); NotifyContext notifyContext = new NotifyContext(subscription.getSubscriptionId(), new URI(originator)); notifyContext.setContextElementResponseList(contextElementResponseList); String providerUrl = subscription.getSubscribeContext().getReference().toString(); HttpHeaders httpHeaders = ngsiClient.getRequestHeaders(providerUrl); logger.debug("=> notifyContext to {} with Content-Type {}", providerUrl, httpHeaders.getContentType()); ngsiClient.notifyContextCustomURL(providerUrl, httpHeaders, notifyContext).addCallback( notifyContextResponse -> logNotifyContextResponse(notifyContextResponse, providerUrl), throwable -> logger.warn("NotifyContext failed for {}", providerUrl, throwable)); } } UpdateContextResponse updateContextResponse = new UpdateContextResponse(); updateContextResponse.setContextElementResponses(contextElementResponseList); return updateContextResponse; } @Override public QueryContextResponse queryContext(final QueryContext query) throws ExecutionException, InterruptedException, MissingRemoteBrokerException { logger.debug("<= queryContext on entities: {}", query.getEntityIdList().toString()); Set<String> attributes = new HashSet<>(); if (query.getAttributeList() != null) { attributes.addAll(query.getAttributeList()); } //TODO : search providingApplication for all entities of queryContext Iterator<URI> providingApplication = localRegistrations.findProvidingApplication(query.getEntityIdList().get(0), attributes); if (providingApplication.hasNext()) { // forward to providing application final String providerUrl = providingApplication.next().toString(); HttpHeaders httpHeaders = ngsiClient.getRequestHeaders(providerUrl); logger.debug("=> queryContext forwarded to : {} with Content-Type {}", providerUrl, httpHeaders.getContentType()); return ngsiClient.queryContext(providerUrl, httpHeaders, query).get(); } String brokerUrl = configuration.getRemoteUrl(); if (brokerUrl == null || brokerUrl.isEmpty()) { throw new MissingRemoteBrokerException("No remote.url parameter defined to forward queryContext"); } // forward query to remote broker HttpHeaders httpHeaders = getRemoteBrokerHeaders(brokerUrl); logger.debug("=> queryContext forwarded to remote broker : {} with Content-Type : {}", brokerUrl, httpHeaders.getContentType()); return ngsiClient.queryContext(brokerUrl, httpHeaders, query).get(); } @Override public SubscribeContextResponse subscribeContext(final SubscribeContext subscribe) throws SubscriptionException, SubscriptionPersistenceException { logger.debug("<= subscribeContext on entities: {}", subscribe.getEntityIdList().toString()); SubscribeContextResponse subscribeContextResponse = new SubscribeContextResponse(); SubscribeResponse subscribeResponse = new SubscribeResponse(); //add the subscription and return subscriptionId String subscriptionId = subscriptions.addSubscription(subscribe); subscribeResponse.setSubscriptionId(subscriptionId); //return in the response the duration because it is set by the subscriptions class if the duration is null in the request subscribeResponse.setDuration(subscriptions.getSubscription(subscriptionId).getSubscribeContext().getDuration()); subscribeContextResponse.setSubscribeResponse(subscribeResponse); return subscribeContextResponse; } /** * Unsupported, will throw an UnsupportedOperationException * @throws UnsupportedOperationException */ @Override public UpdateContextSubscriptionResponse updateContextSubscription(final UpdateContextSubscription updateContextSubscription) throws Exception { return super.updateContextSubscription(updateContextSubscription); } @Override public UnsubscribeContextResponse unsubscribeContext(final UnsubscribeContext unsubscribe) throws SubscriptionPersistenceException { logger.debug("<= unsubscribeContext with subscriptionId: {}", unsubscribe.getSubscriptionId()); String subscriptionId = unsubscribe.getSubscriptionId(); StatusCode statusCode; if (subscriptions.deleteSubscription(unsubscribe)) { statusCode = new StatusCode(CodeEnum.CODE_200); } else { statusCode = new StatusCode(CodeEnum.CODE_470, subscriptionId); } return new UnsubscribeContextResponse(statusCode, subscriptionId); } @ExceptionHandler(RegistrationException.class) public ResponseEntity<Object> registrationExceptionHandler(HttpServletRequest req, RegistrationException registrationException) { logger.error("Registration error: {}", registrationException.getMessage()); StatusCode statusCode = new StatusCode(); statusCode.setCode("400"); statusCode.setReasonPhrase("registration error"); statusCode.setDetail(registrationException.getMessage()); return errorResponse(req.getRequestURI(), statusCode); } @ExceptionHandler(MissingRemoteBrokerException.class) public ResponseEntity<Object> missingRemoteBrokerExceptionHandler(HttpServletRequest req, MissingRemoteBrokerException missingRemoteBrokerException) { logger.error("MissingRemoteBrokerException error: {}", missingRemoteBrokerException.getMessage()); StatusCode statusCode = new StatusCode(); statusCode.setCode("500"); statusCode.setReasonPhrase("missing remote broker error"); statusCode.setDetail(missingRemoteBrokerException.getMessage()); return errorResponse(req.getRequestURI(), statusCode); } @ExceptionHandler(SubscriptionException.class) public ResponseEntity<Object> subscriptionExceptionHandler(HttpServletRequest req, SubscriptionException subscriptionException) { logger.error("Subscription error: {}", subscriptionException.getMessage()); StatusCode statusCode = new StatusCode(); statusCode.setCode("400"); statusCode.setReasonPhrase("subscription error"); statusCode.setDetail(subscriptionException.getMessage()); return errorResponse(req.getRequestURI(), statusCode); } @ExceptionHandler(SubscriptionPersistenceException.class) public ResponseEntity<Object> subscriptionPersistenceExceptionHandler(HttpServletRequest req, SubscriptionPersistenceException subscriptionPersistenceException) { logger.error("SubscriptionPersistenceException error: {}", subscriptionPersistenceException.getMessage()); StatusCode statusCode = new StatusCode(); statusCode.setCode("500"); statusCode.setReasonPhrase("error in subscription persistence"); statusCode.setDetail(subscriptionPersistenceException.getMessage()); return errorResponse(req.getRequestURI(), statusCode); } @ExceptionHandler(RegistrationPersistenceException.class) public ResponseEntity<Object> registrationPersistenceExceptionHandler(HttpServletRequest req, RegistrationPersistenceException registrationPersistenceException) { logger.error("RegistrationPersistenceException error: {}", registrationPersistenceException.getMessage()); StatusCode statusCode = new StatusCode(); statusCode.setCode("500"); statusCode.setReasonPhrase("error in registration persistence"); statusCode.setDetail(registrationPersistenceException.getMessage()); return errorResponse(req.getRequestURI(), statusCode); } private HttpHeaders getRemoteBrokerHeaders(String brokerUrl) { HttpHeaders httpHeaders = ngsiClient.getRequestHeaders(brokerUrl); configuration.addRemoteHeaders(httpHeaders); return httpHeaders; } private void logUpdateContextResponse(UpdateContextResponse updateContextResponse, String brokerUrl) { if (updateContextResponse.getErrorCode() != null) { logger.warn("UpdateContext failed for {}: {}", brokerUrl, updateContextResponse.getErrorCode().toString()); } else { updateContextResponse.getContextElementResponses().forEach(contextElementResponse -> { if (contextElementResponse.getStatusCode().getCode().equals(CodeEnum.CODE_200.getLabel())) { logger.debug("UpdateContext completed for {} ", brokerUrl); } else { logger.warn("UpdateContext failed for {}: entityId {} {}", brokerUrl, contextElementResponse.getContextElement().getEntityId().getId(), contextElementResponse.getStatusCode().toString()); } }); } } private void logNotifyContextResponse(NotifyContextResponse notifyContextResponse, String providerUrl) { if (notifyContextResponse.getResponseCode().getCode().equals(CodeEnum.CODE_200.getLabel())) { logger.debug("NotifyContext completed for {} ", providerUrl); } else { logger.warn("NotifyContext failed for {}: {}", providerUrl, notifyContextResponse.getResponseCode().toString()); } } }