io.syndesis.dao.manager.DataManager.java Source code

Java tutorial

Introduction

Here is the source code for io.syndesis.dao.manager.DataManager.java

Source

/**
 * Copyright (C) 2016 Red Hat, Inc.
 *
 * Licensed 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 io.syndesis.dao.manager;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

import javax.annotation.PostConstruct;
import javax.persistence.EntityExistsException;
import javax.persistence.EntityNotFoundException;

import io.syndesis.core.EventBus;
import io.syndesis.core.KeyGenerator;
import io.syndesis.core.SyndesisServerException;
import io.syndesis.dao.init.ModelData;
import io.syndesis.dao.init.ReadApiClientData;
import io.syndesis.model.ChangeEvent;
import io.syndesis.model.Kind;
import io.syndesis.model.ListResult;
import io.syndesis.model.WithId;

import org.infinispan.Cache;
import org.infinispan.manager.CacheContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class DataManager implements DataAccessObjectRegistry {

    private static final Logger LOGGER = LoggerFactory.getLogger(DataManager.class.getName());

    private final CacheContainer caches;
    private final EventBus eventBus;

    @Value("${deployment.file:io/syndesis/dao/deployment.json}")
    @SuppressWarnings("PMD.ImmutableField") // @Value cannot be applied to final properties
    private String dataFileName = "io/syndesis/dao/deployment.json";
    @SuppressWarnings("PMD.ImmutableField") // @Value cannot be applied to final properties
    @Value("${deployment.load-demo-data:true}")
    private boolean loadDemoData = true;

    private final List<DataAccessObject<?>> dataAccessObjects = new ArrayList<>();
    private final Map<Class<? extends WithId<?>>, DataAccessObject<?>> dataAccessObjectMapping = new ConcurrentHashMap<>();

    // Inject mandatory via constructor injection.
    @Autowired
    public DataManager(CacheContainer caches, List<DataAccessObject<?>> dataAccessObjects, EventBus eventBus) {
        this.caches = caches;
        this.eventBus = eventBus;
        if (dataAccessObjects != null) {
            this.dataAccessObjects.addAll(dataAccessObjects);
        }
    }

    @PostConstruct
    public void init() {
        for (DataAccessObject<?> dataAccessObject : dataAccessObjects) {
            registerDataAccessObject(dataAccessObject);
        }
    }

    public void resetDeploymentData() {
        if (dataFileName != null) {
            loadData(this.dataFileName);
        }
        if (loadDemoData) {
            loadData("io/syndesis/dao/demo-data.json");
        }
    }

    private void loadData(String file) {
        ReadApiClientData reader = new ReadApiClientData();
        try {
            List<ModelData<?>> mdList = reader.readDataFromFile(file);
            for (ModelData<?> modelData : mdList) {
                store(modelData);
            }
        } catch (@SuppressWarnings("PMD.AvoidCatchingGenericException") Exception e) {
            throw new IllegalStateException("Cannot read dummy startup data due to: " + e.getMessage(), e);
        }
    }

    public <T extends WithId<T>> void store(ModelData<T> modelData) {
        try {
            Kind kind = modelData.getKind();

            LOGGER.debug("{}:{}", kind, modelData.getDataAsJson());
            T entity = modelData.getData();
            Optional<String> id = entity.getId();
            if (!id.isPresent()) {
                LOGGER.warn("Cannot load entity from file since it's missing an id: {}", modelData.toJson());
            } else {
                WithId<?> prev = null;
                try {
                    prev = this.<T>fetch(kind.getModelClass(), id.get());
                } catch (@SuppressWarnings("PMD.AvoidCatchingGenericException") RuntimeException e) {
                    // Lets try to wipe out the previous record in case
                    // we are running into something like a schema change.
                    this.<T>delete(kind.getModelClass(), id.get());
                }
                if (prev == null) {
                    create(entity);
                } else {
                    update(entity);
                }
            }
        } catch (@SuppressWarnings("PMD.AvoidCatchingGenericException") Exception e) {
            LOGGER.warn("Cannot load entity from file: ", e);
            throw SyndesisServerException.launderThrowable(e);
        }
    }

    public <T extends WithId<T>> ListResult<T> fetchAll(Class<T> model,
            Function<ListResult<T>, ListResult<T>>... operators) {

        ListResult<T> result;
        if (getDataAccessObject(model) != null) {
            result = doWithDataAccessObject(model, d -> d.fetchAll());
        } else {
            Kind kind = Kind.from(model);
            Cache<String, T> cache = caches.getCache(kind.getModelName());
            result = ListResult.of(cache.values());
        }

        for (Function<ListResult<T>, ListResult<T>> operator : operators) {
            result = operator.apply(result);
        }
        return result;
    }

    public <T extends WithId<T>> T fetch(Class<T> model, String id) {
        Kind kind = Kind.from(model);
        Map<String, T> cache = caches.getCache(kind.getModelName());

        T value = cache.get(id);
        if (value == null) {
            value = this.<T, T>doWithDataAccessObject(model, d -> d.fetch(id));
            if (value != null) {
                cache.put(id, value);
            }
        }
        return value;
    }

    public <T extends WithId<T>> Set<String> fetchIdsByPropertyValue(Class<T> model, String property,
            String value) {
        return doWithDataAccessObject(model, d -> d.fetchIdsByPropertyValue(property, value));
    }

    public <T extends WithId<T>> T create(final T entity) {
        Kind kind = entity.getKind();
        Cache<String, T> cache = caches.getCache(kind.getModelName());
        Optional<String> id = entity.getId();
        String idVal;

        final T entityToCreate;
        if (!id.isPresent()) {
            idVal = KeyGenerator.createKey();
            entityToCreate = entity.withId(idVal);
        } else {
            idVal = id.get();
            if (cache.keySet().contains(idVal)) {
                throw new EntityExistsException("There already exists a " + kind + " with id " + idVal);
            }
            entityToCreate = entity;
        }

        this.<T, T>doWithDataAccessObject(kind.getModelClass(), d -> d.create(entityToCreate));
        cache.put(idVal, entityToCreate);
        broadcast("created", kind.getModelName(), idVal);
        return entityToCreate;
    }

    public <T extends WithId<T>> void update(T entity) {
        Optional<String> id = entity.getId();
        if (!id.isPresent()) {
            throw new EntityNotFoundException("Setting the id on the entity is required for updates");
        }

        String idVal = id.get();

        Kind kind = entity.getKind();
        T previous = this.<T, T>doWithDataAccessObject(kind.getModelClass(), d -> d.update(entity));

        Map<String, T> cache = caches.getCache(kind.getModelName());
        if (!cache.containsKey(idVal) && previous == null) {
            throw new EntityNotFoundException("Can not find " + kind + " with id " + idVal);
        }

        cache.put(idVal, entity);
        broadcast("updated", kind.getModelName(), idVal);

        //TODO 1. properly merge the data ? + add data validation in the REST Resource
    }

    public <T extends WithId<T>> boolean delete(Class<T> model, String id) {
        if (id == null || id.equals("")) {
            throw new EntityNotFoundException("Setting the id on the entity is required for updates");
        }

        Kind kind = Kind.from(model);
        Map<String, WithId<T>> cache = caches.getCache(kind.getModelName());

        // Remove it out of the cache
        WithId<T> entity = cache.remove(id);
        boolean deletedInCache = entity != null;

        // And out of the DAO
        boolean deletedFromDAO = Boolean.TRUE.equals(doWithDataAccessObject(model, d -> d.delete(id)));

        // Return true if the entity was found in any of the two.
        if (deletedInCache || deletedFromDAO) {
            broadcast("deleted", kind.getModelName(), id);
            return true;
        }

        return false;
    }

    public <T extends WithId<T>> void deleteAll(Class<T> model) {
        Kind kind = Kind.from(model);
        Map<String, WithId<T>> cache = caches.getCache(kind.getModelName());
        cache.clear();

        doWithDataAccessObject(model, d -> {
            d.deleteAll();
            return null;
        });
    }

    @Override
    public Map<Class<? extends WithId<?>>, DataAccessObject<?>> getDataAccessObjectMapping() {
        return dataAccessObjectMapping;
    }

    /**
     * Perform a simple action if a {@link DataAccessObject} for the specified kind exists.
     * This is just a way to avoid, duplicating the dao lookup and checks, which are going to change.
     * @param model         The model class of the {@link DataAccessObject}.
     * @param function      The function to perfom on the {@link DataAccessObject}.
     * @param <O>           The return type.
     * @return              The outcome of the function.
     */
    private <T extends WithId<T>, R> R doWithDataAccessObject(Class<T> model,
            Function<DataAccessObject<T>, R> function) {
        DataAccessObject<T> dataAccessObject = getDataAccessObject(model);
        if (dataAccessObject != null) {
            return function.apply(dataAccessObject);
        }
        return null;
    }

    private void broadcast(String event, String type, String id) {
        if (eventBus != null) {
            eventBus.broadcast("change-event", ChangeEvent.of(event, type, id).toJson());
        }
    }

    public void clearCache() {
        for (Kind kind : Kind.values()) {
            caches.getCache(kind.modelName).clear();
        }
    }

}