com.gst.infrastructure.hooks.service.HookWritePlatformServiceJpaRepositoryImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.gst.infrastructure.hooks.service.HookWritePlatformServiceJpaRepositoryImpl.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package com.gst.infrastructure.hooks.service;

import static com.gst.infrastructure.hooks.api.HookApiConstants.actionNameParamName;
import static com.gst.infrastructure.hooks.api.HookApiConstants.configParamName;
import static com.gst.infrastructure.hooks.api.HookApiConstants.contentTypeName;
import static com.gst.infrastructure.hooks.api.HookApiConstants.entityNameParamName;
import static com.gst.infrastructure.hooks.api.HookApiConstants.eventsParamName;
import static com.gst.infrastructure.hooks.api.HookApiConstants.nameParamName;
import static com.gst.infrastructure.hooks.api.HookApiConstants.payloadURLName;
import static com.gst.infrastructure.hooks.api.HookApiConstants.templateIdParamName;
import static com.gst.infrastructure.hooks.api.HookApiConstants.webTemplateName;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.persistence.PersistenceException;

import org.apache.commons.lang.exception.ExceptionUtils;
import com.gst.infrastructure.core.api.JsonCommand;
import com.gst.infrastructure.core.data.ApiParameterError;
import com.gst.infrastructure.core.data.CommandProcessingResult;
import com.gst.infrastructure.core.data.CommandProcessingResultBuilder;
import com.gst.infrastructure.core.data.DataValidatorBuilder;
import com.gst.infrastructure.core.exception.PlatformApiDataValidationException;
import com.gst.infrastructure.core.exception.PlatformDataIntegrityException;
import com.gst.infrastructure.core.serialization.FromJsonHelper;
import com.gst.infrastructure.hooks.domain.Hook;
import com.gst.infrastructure.hooks.domain.HookConfiguration;
import com.gst.infrastructure.hooks.domain.HookRepository;
import com.gst.infrastructure.hooks.domain.HookResource;
import com.gst.infrastructure.hooks.domain.HookTemplate;
import com.gst.infrastructure.hooks.domain.HookTemplateRepository;
import com.gst.infrastructure.hooks.domain.Schema;
import com.gst.infrastructure.hooks.exception.HookNotFoundException;
import com.gst.infrastructure.hooks.exception.HookTemplateNotFoundException;
import com.gst.infrastructure.hooks.processor.ProcessorHelper;
import com.gst.infrastructure.hooks.processor.WebHookService;
import com.gst.infrastructure.hooks.serialization.HookCommandFromApiJsonDeserializer;
import com.gst.infrastructure.security.service.PlatformSecurityContext;
import com.gst.template.domain.Template;
import com.gst.template.domain.TemplateRepository;
import com.gst.template.exception.TemplateNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

import retrofit.RetrofitError;

@Service
public class HookWritePlatformServiceJpaRepositoryImpl implements HookWritePlatformService {

    private final PlatformSecurityContext context;
    private final HookRepository hookRepository;
    private final HookTemplateRepository hookTemplateRepository;
    private final TemplateRepository ugdTemplateRepository;
    private final HookCommandFromApiJsonDeserializer fromApiJsonDeserializer;
    private final FromJsonHelper fromApiJsonHelper;

    @Autowired
    public HookWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
            final HookRepository hookRepository, final HookTemplateRepository hookTemplateRepository,
            final TemplateRepository ugdTemplateRepository,
            final HookCommandFromApiJsonDeserializer fromApiJsonDeserializer,
            final FromJsonHelper fromApiJsonHelper) {
        this.context = context;
        this.hookRepository = hookRepository;
        this.hookTemplateRepository = hookTemplateRepository;
        this.ugdTemplateRepository = ugdTemplateRepository;
        this.fromApiJsonDeserializer = fromApiJsonDeserializer;
        this.fromApiJsonHelper = fromApiJsonHelper;
    }

    @Transactional
    @Override
    @CacheEvict(value = "hooks", allEntries = true)
    public CommandProcessingResult createHook(final JsonCommand command) {

        try {
            this.context.authenticatedUser();

            this.fromApiJsonDeserializer.validateForCreate(command.json());

            final HookTemplate template = retrieveHookTemplateBy(
                    command.stringValueOfParameterNamed(nameParamName));
            final String configJson = command.jsonFragment(configParamName);
            final Set<HookConfiguration> config = assembleConfig(command.mapValueOfParameterNamed(configJson),
                    template);
            final JsonArray events = command.arrayOfParameterNamed(eventsParamName);
            final Set<HookResource> allEvents = assembleSetOfEvents(events);
            Template ugdTemplate = null;
            if (command.hasParameter(templateIdParamName)) {
                final Long ugdTemplateId = command.longValueOfParameterNamed(templateIdParamName);
                ugdTemplate = this.ugdTemplateRepository.findOne(ugdTemplateId);
                if (ugdTemplate == null) {
                    throw new TemplateNotFoundException(ugdTemplateId);
                }
            }
            final Hook hook = Hook.fromJson(command, template, config, allEvents, ugdTemplate);

            validateHookRules(template, config, allEvents);

            this.hookRepository.save(hook);

            return new CommandProcessingResultBuilder().withCommandId(command.commandId())
                    .withEntityId(hook.getId()).build();
        } catch (final DataIntegrityViolationException dve) {
            handleHookDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
            return CommandProcessingResult.empty();
        } catch (final PersistenceException dve) {
            Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
            handleHookDataIntegrityIssues(command, throwable, dve);
            return CommandProcessingResult.empty();
        }
    }

    @Transactional
    @Override
    @CacheEvict(value = "hooks", allEntries = true)
    public CommandProcessingResult updateHook(final Long hookId, final JsonCommand command) {

        try {
            this.context.authenticatedUser();

            this.fromApiJsonDeserializer.validateForUpdate(command.json());

            final Hook hook = retrieveHookBy(hookId);
            final HookTemplate template = hook.getHookTemplate();
            final Map<String, Object> changes = hook.update(command);

            if (!changes.isEmpty()) {

                if (changes.containsKey(templateIdParamName)) {
                    final Long ugdTemplateId = command.longValueOfParameterNamed(templateIdParamName);
                    final Template ugdTemplate = this.ugdTemplateRepository.findOne(ugdTemplateId);
                    if (ugdTemplate == null) {
                        changes.remove(templateIdParamName);
                        throw new TemplateNotFoundException(ugdTemplateId);
                    }
                    hook.updateUgdTemplate(ugdTemplate);
                }

                if (changes.containsKey(eventsParamName)) {
                    final Set<HookResource> events = assembleSetOfEvents(
                            command.arrayOfParameterNamed(eventsParamName));
                    final boolean updated = hook.updateEvents(events);
                    if (!updated) {
                        changes.remove(eventsParamName);
                    }
                }

                if (changes.containsKey(configParamName)) {
                    final String configJson = command.jsonFragment(configParamName);
                    final Set<HookConfiguration> config = assembleConfig(
                            command.mapValueOfParameterNamed(configJson), template);
                    final boolean updated = hook.updateConfig(config);
                    if (!updated) {
                        changes.remove(configParamName);
                    }
                }

                this.hookRepository.saveAndFlush(hook);
            }

            return new CommandProcessingResultBuilder() //
                    .withCommandId(command.commandId()) //
                    .withEntityId(hookId) //
                    .with(changes) //
                    .build();
        } catch (final DataIntegrityViolationException dve) {
            handleHookDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
            return CommandProcessingResult.empty();
        } catch (final PersistenceException dve) {
            Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
            handleHookDataIntegrityIssues(command, throwable, dve);
            return CommandProcessingResult.empty();
        }
    }

    @Transactional
    @Override
    @CacheEvict(value = "hooks", allEntries = true)
    public CommandProcessingResult deleteHook(final Long hookId) {

        this.context.authenticatedUser();
        final Hook hook = retrieveHookBy(hookId);
        try {
            this.hookRepository.delete(hook);
        } catch (final DataIntegrityViolationException e) {
            throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
                    "Unknown data integrity issue with resource: " + e.getMostSpecificCause());
        }
        return new CommandProcessingResultBuilder().withEntityId(hookId).build();
    }

    private Hook retrieveHookBy(final Long hookId) {
        final Hook hook = this.hookRepository.findOne(hookId);
        if (hook == null) {
            throw new HookNotFoundException(hookId);
        }
        return hook;
    }

    private HookTemplate retrieveHookTemplateBy(final String templateName) {
        final HookTemplate template = this.hookTemplateRepository.findOne(templateName);
        if (template == null) {
            throw new HookTemplateNotFoundException(templateName);
        }
        return template;
    }

    private Set<HookConfiguration> assembleConfig(final Map<String, String> hookConfig,
            final HookTemplate template) {

        final Set<HookConfiguration> configuration = new HashSet<>();
        final Set<Schema> fields = template.getSchema();

        for (final Entry<String, String> configEntry : hookConfig.entrySet()) {
            for (final Schema field : fields) {
                final String fieldName = field.getFieldName();
                if (fieldName.equalsIgnoreCase(configEntry.getKey())) {

                    final HookConfiguration config = HookConfiguration.createNewWithoutHook(field.getFieldType(),
                            configEntry.getKey(), configEntry.getValue());
                    configuration.add(config);
                    break;
                }
            }

        }

        return configuration;
    }

    private Set<HookResource> assembleSetOfEvents(final JsonArray eventsArray) {

        final Set<HookResource> allEvents = new HashSet<>();

        for (int i = 0; i < eventsArray.size(); i++) {

            final JsonObject eventElement = eventsArray.get(i).getAsJsonObject();

            final String entityName = this.fromApiJsonHelper.extractStringNamed(entityNameParamName, eventElement);
            final String actionName = this.fromApiJsonHelper.extractStringNamed(actionNameParamName, eventElement);
            final HookResource event = HookResource.createNewWithoutHook(entityName, actionName);
            allEvents.add(event);
        }

        return allEvents;
    }

    private void validateHookRules(final HookTemplate template, final Set<HookConfiguration> config,
            Set<HookResource> events) {

        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
        final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
                .resource("hook");

        if (!template.getName().equalsIgnoreCase(webTemplateName)
                && this.hookRepository.findOneByTemplateId(template.getId()) != null) {
            final String errorMessage = "multiple.non.web.template.hooks.not.supported";
            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(errorMessage);
        }

        for (final HookConfiguration conf : config) {
            final String fieldValue = conf.getFieldValue();
            if (conf.getFieldName().equals(contentTypeName)) {
                if (!(fieldValue.equalsIgnoreCase("json") || fieldValue.equalsIgnoreCase("form"))) {
                    final String errorMessage = "content.type.must.be.json.or.form";
                    baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(errorMessage);
                }
            }

            if (conf.getFieldName().equals(payloadURLName)) {
                try {
                    final WebHookService service = ProcessorHelper.createWebHookService(fieldValue);
                    service.sendEmptyRequest();
                } catch (RetrofitError re) {
                    // Swallow error if it's because of method not supported or
                    // if url throws 404 - required for integration test,
                    // url generated on 1st POST request
                    if (re.getResponse() == null) {
                        String errorMessage = "url.invalid";
                        baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(errorMessage);
                    }
                }
            }
        }

        if (events == null || events.isEmpty()) {
            final String errorMessage = "registered.events.cannot.be.empty";
            baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(errorMessage);
        }

        final Set<Schema> fields = template.getSchema();
        for (final Schema field : fields) {
            if (!field.isOptional()) {
                boolean found = false;
                for (final HookConfiguration conf : config) {
                    if (field.getFieldName().equals(conf.getFieldName())) {
                        found = true;
                    }
                }
                if (!found) {
                    final String errorMessage = "required.config.field." + "not.provided";
                    baseDataValidator.reset().value(field.getFieldName())
                            .failWithCodeNoParameterAddedToErrorCode(errorMessage);
                }
            }
        }

        if (!dataValidationErrors.isEmpty()) {
            throw new PlatformApiDataValidationException(dataValidationErrors);
        }
    }

    private void handleHookDataIntegrityIssues(final JsonCommand command, final Throwable realCause,
            final Exception dve) {
        if (realCause.getMessage().contains("hook_name")) {
            final String name = command.stringValueOfParameterNamed("name");
            throw new PlatformDataIntegrityException("error.msg.hook.duplicate.name",
                    "A hook with name '" + name + "' already exists", "name", name);
        }

        throw new PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
                "Unknown data integrity issue with resource: " + realCause.getMessage());
    }
}