@SuppressWarnings("rawtypes") // CompositeFuture.all(list) requires raw futures.
    @Override
    public void registerClient(Client client, IAsyncResultHandler<Void> resultHandler) {
        List<Future> futures = new ArrayList<>(client.getContracts().size());
        List<Contract> contracts = new ArrayList<>(client.getContracts());
        String clientIndex = getClientIndex(client);

        // Future for each contract and execute get.
        for (Contract contract : contracts) {
            Future future = Future.future();
            futures.add(future);
            String apiIndex = getApiIndex(contract.getApiOrgId(), contract.getApiId(), contract.getApiVersion());
            objectMap.get(apiIndex, future.completer());
        }

        CompositeFuture.all(futures).setHandler(compositeResult -> {
            if (compositeResult.succeeded()) {
                // If any contract didn't correspond to a stored API.
                Contract failedContract = null;
                for (int i=0; i<futures.size(); i++) {
                    if (futures.get(i).result() == null) {
                        failedContract = contracts.get(0);
                        break;
                    }
                }
                // If we found an invalid contract.
                if (failedContract != null) {
                    Exception ex = new ApiNotFoundException(Messages.i18n.format("InMemoryRegistry.ApiNotFoundInOrg",
                            failedContract.getApiId(), failedContract.getApiOrgId()));
                    resultHandler.handle(AsyncResultImpl.create(ex));
                } else {
                    Future<Object> putNewApiKeyFuture = Future.future();
                    Future<Object> endFuture = Future.future();

                    // Order: Create new API Key reference; Replace old ID -> API mapping; Delete old key reference)
                    // This should ensure no breaking/irreconcilable behaviour.
                    objectMap.putIfAbsent(client.getApiKey(), client, putNewApiKeyFuture.completer());

                    // Replace API Key reference
                    putNewApiKeyFuture.compose(clientWithSameApiKey -> {
                        Future<Object> replaceClientFuture = Future.future();
                        // There's a small chance the same key will replace the old one, usually
                        // only in hard-coded tests. Generally sameKeyReplace will be null.
                        if (clientWithSameApiKey != null) {
                            //System.err.println("!!!!! Same API Key -- Replacing. Must not delete later. !!!!!!");
                            objectMap.replace(client.getApiKey(), client, replaceClientFuture.completer());
                        } else {
                            objectMap.putIfAbsent(clientIndex, client, replaceClientFuture.completer());
                        }
                        return replaceClientFuture;
                    // Remove old API key reference
                    }).compose(oldClientRaw -> {
                        Client oldClient = (Client) oldClientRaw;
                        if (oldClientRaw != null && !oldClient.getApiKey().equals(client.getApiKey())) {
                            objectMap.remove(oldClient.getApiKey(), endFuture.completer());
                        } else {
                            endFuture.complete();
                        }
                    }, endFuture)
                    // When finished, call this handler and then resultHandler
                    .setHandler(handleResult(resultHandler));
                }
            } else {
                resultHandler.handle(AsyncResultImpl.create(compositeResult.cause()));
            }
        });
    }
@author Marc Savy {@literal <marc@rhymewithgravy.com>}
gateway/engine/vertx-shareddata/src/main/java/io/apiman/gateway/engine/vertx/shareddata/SharedGlobalDataRegistry.java