class IntegrationUpdateHandler extends AbstractResourceUpdateHandler<IntegrationBulletinBoard> {

    @SuppressWarnings({"PMD.ExcessiveMethodLength", "PMD.NPathComplexity"})
    @Override
    protected List<IntegrationBulletinBoard> compute(final ChangeEvent event) {
        final List<IntegrationBulletinBoard> boards = new ArrayList<>();
        final DataManager dataManager = getDataManager();

        /*
         * Assemble all the artifact collections that are going to be checked in
         * the data manager. This does it once rather than repeatedly in the
         * various loops since getting stuff from the DataManager is quite
         * expensive and slow.
         *
         * see #3717
         */
        final List<Integration> integrations = dataManager.fetchAll(Integration.class).getItems();
        final List<IntegrationDeployment> deployments = dataManager.fetchAll(IntegrationDeployment.class).getItems();
        final Set<Integration> deployedIintegrations = deployments.stream()
            .map(d -> d.getSpec())
            .collect(Collectors.toSet());
        final List<Connection> connections = dataManager.fetchAll(Connection.class).getItems();
        final Collection<Integration> allIntegrations = new ArrayList<>(integrations.size() + deployedIintegrations.size());
        allIntegrations.addAll(integrations);
        allIntegrations.addAll(deployedIintegrations);

        final List<LeveledMessage> messages = new ArrayList<>();

        for (final Integration integration : integrations) {
            final String id = integration.getId().get();

            final IntegrationBulletinBoard board = dataManager.fetchByPropertyValue(IntegrationBulletinBoard.class, "targetResourceId", id).orElse(null);
            final IntegrationBulletinBoard.Builder builder;

            if (board != null) {
                builder = new IntegrationBulletinBoard.Builder()
                    .createFrom(board);
            } else {
                builder = new IntegrationBulletinBoard.Builder()
                    .id(KeyGenerator.createKey())
                    .targetResourceId(id)
                    .createdAt(System.currentTimeMillis());
            }

            // reuse messages
            messages.clear();

            final List<Flow> flows = integration.getFlows();

            for (int f = 0; f < flows.size(); f++) {
                final Flow flow = flows.get(f);
                final String flowId = flow.getId().orElse("flow-" + f);
                final List<Step> steps = flow.getSteps();

                // the following code is quite ugly but the integration model
                // duplicates
                // the resources definition in every step so we definitively
                // need to
                // shrink the model and avoid duplication !
                //
                // A step should only include the user defined property and a
                // reference
                // to the resources the properties apply to
                for (int s = 0; s < steps.size(); s++) {
                    final Step step = steps.get(s);
                    final String stepId = step.getId().orElse("step-" + s);
                    final Supplier<LeveledMessage.Builder> supplier = () -> new LeveledMessage.Builder().putMetadata("flow", flowId).putMetadata("step",
                        stepId);

                    if (!step.getAction().isPresent()) {
                        continue;
                    }

                    // **********************
                    // Integration
                    // **********************

                    final TargetWithDomain<Integration> intTarget = new IntegrationWithDomain(integration, allIntegrations);
                    messages.addAll(computeValidatorMessages(supplier, intTarget));

                    // **********************
                    // Extension Action
                    // **********************

                    step.getAction().filter(StepAction.class::isInstance).map(StepAction.class::cast).ifPresent(action -> {
                        if (!step.getExtension().isPresent()) {
                            return;
                        }

                        final Extension extension = step.getExtension().get();

                        // When an extension is updated a new entity is written
                        // to the db
                        // so we can't simply lookup by ID but whe need to
                        // search for the
                        // latest installed extension.
                        //
                        // This fetchIdsByPropertyValue is not really optimized
                        // as it
                        // ends up in multiple statements sent to the db, we
                        // maybe need
                        // to have a better support for this use-case.
                        final Set<String> ids = dataManager.fetchIdsByPropertyValue(Extension.class,
                            "extensionId", extension.getExtensionId(),
                            "status", Extension.Status.Installed.name());

                        // No installed extension found, this happen if the
                        // extension
                        // has been deleted.
                        if (ids.size() == 0) {
                            messages.add(
                                supplier.get()
                                    .level(LeveledMessage.Level.WARN)
                                    .code(LeveledMessage.Code.SYNDESIS004)
                                    .build());

                            return;
                        }

                        // More than one installed extension found, this should
                        // not
                        // happen unless something wrong happen at extension
                        // activation
                        // phase
                        if (ids.size() > 1) {
                            messages.add(
                                supplier.get()
                                    .level(LeveledMessage.Level.WARN)
                                    .code(LeveledMessage.Code.SYNDESIS010)
                                    .build());

                            return;
                        }

                        final Extension newExtension = dataManager.fetch(Extension.class, ids.iterator().next());
                        if (newExtension == null) {
                            messages.add(
                                supplier.get()
                                    .level(LeveledMessage.Level.WARN)
                                    .code(LeveledMessage.Code.SYNDESIS004)
                                    .build());
                        } else {
                            final Action newAction = newExtension.findActionById(action.getId().get()).orElse(null);
                            if (newAction == null) {
                                messages.add(
                                    supplier.get()
                                        .level(LeveledMessage.Level.WARN)
                                        .code(LeveledMessage.Code.SYNDESIS005)
                                        .build());
                            } else {
                                messages.addAll(computePropertiesDiffMessages(supplier, action.getProperties(), newAction.getProperties()));
                                messages.addAll(computeMissingMandatoryPropertiesMessages(supplier, newAction.getProperties(), step.getConfiguredProperties()));
                                messages.addAll(computeSecretsUpdateMessages(supplier, newAction.getProperties(), step.getConfiguredProperties()));
                            }
                        }
                    });

                    // **********************
                    // Connector Action
                    // **********************

                    step.getAction().filter(ConnectorAction.class::isInstance).map(ConnectorAction.class::cast).ifPresent(action -> {
                        if (!step.getConnection().isPresent()) {
                            return;
                        }

                        final Connection connection = step.getConnection().get();
                        final Connection dbConnection = getDataManager().fetch(Connection.class, connection.getId().get());

                        if (dbConnection != null &&
                            Kind.Integration.equals(event.getKind().map(Kind::from).orElse(null))) {
                            /*
                             * An integration event will create, delete or
                             * update it. This has an impact on associated
                             * connections in that their augmented properties,
                             * ie. those appended by the {@link
                             * ConnectionHandler#augmentedWithUsage}, eg. uses,
                             * need to be re-synced by clients. In order to do
                             * that, a change event must be broadcast to all
                             * clients and a call to update() provides such an
                             * event.
                             *
                             * see #4008
                             */
                            dataManager.update(dbConnection);
                        }

                        if (connection.getId().isPresent() && (!connection.getConnector().isPresent() || !connection.getConnector().get().getTags().contains("dynamic"))) {
                            //
                            // Compare the connection in the draft integration's
                            // step
                            // with the connection from the data manager and log
                            // the
                            // result using the given message codes
                            //
                            compareConnection(
                                connection,
                                dbConnection,
                                messages,
                                supplier,
                                LeveledMessage.Code.SYNDESIS009,
                                LeveledMessage.Code.SYNDESIS011);
                        }

                        final Connector newConnector = dataManager.fetch(Connector.class, connection.getConnectorId());
                        if (newConnector == null) {
                            messages.add(
                                supplier.get()
                                    .level(LeveledMessage.Level.WARN)
                                    .code(LeveledMessage.Code.SYNDESIS003)
                                    .build());
                        } else {
                            final Action newAction = newConnector.findActionById(action.getId().get()).orElse(null);
                            if (newAction == null) {
                                messages.add(
                                    supplier.get()
                                        .level(LeveledMessage.Level.WARN)
                                        .code(LeveledMessage.Code.SYNDESIS005)
                                        .build());
                            } else {
                                final Map<String, String> configuredProperties = CollectionsUtils.aggregate(connection.getConfiguredProperties(),
                                    step.getConfiguredProperties());

                                final ConnectionWithDomain connTarget = new ConnectionWithDomain(connection, connections);
                                messages.addAll(computeValidatorMessages(supplier, connTarget));
                                messages.addAll(computePropertiesDiffMessages(supplier, action.getProperties(), newAction.getProperties()));
                                messages.addAll(computeMissingMandatoryPropertiesMessages(supplier, newAction.getProperties(), configuredProperties));
                                messages.addAll(computeSecretsUpdateMessages(supplier, newAction.getProperties(), configuredProperties));
                            }
                        }
                    });

                }
            }

            // **********************
            // Integration Deployments
            // **********************
            messages.addAll(computeDeploymentDifferences(integration, deployments));

            builder.errors(countMessagesWithLevel(LeveledMessage.Level.ERROR, messages));
            builder.warnings(countMessagesWithLevel(LeveledMessage.Level.WARN, messages));
            builder.notices(countMessagesWithLevel(LeveledMessage.Level.INFO, messages));
            builder.putMetadata("integration-id", id);
            builder.putMetadata("integration-version", Integer.toString(integration.getVersion()));
            builder.messages(messages);

            final IntegrationBulletinBoard updated = builder.build();
            if (!updated.equals(board)) {
                boards.add(builder.updatedAt(System.currentTimeMillis()).build());
            }

            // reuse messages
            messages.clear();
        }

        return boards;
    }

    public  IntegrationUpdateHandler(final DataManager dataManager, final EncryptionComponent encryptionComponent, final Validator validator);

}