org.tomitribe.tribestream.registryng.repository.Repository.java Source code

Java tutorial

Introduction

Here is the source code for org.tomitribe.tribestream.registryng.repository.Repository.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 org.tomitribe.tribestream.registryng.repository;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.models.HttpMethod;
import io.swagger.models.Operation;
import io.swagger.models.Path;
import io.swagger.models.Swagger;
import io.swagger.models.parameters.Parameter;
import org.hibernate.envers.AuditReader;
import org.hibernate.envers.AuditReaderFactory;
import org.hibernate.envers.query.AuditEntity;
import org.hibernate.envers.query.AuditQuery;
import org.hibernate.exception.ConstraintViolationException;
import org.tomitribe.tribestream.registryng.cdi.Tribe;
import org.tomitribe.tribestream.registryng.entities.Endpoint;
import org.tomitribe.tribestream.registryng.entities.HistoryEntry;
import org.tomitribe.tribestream.registryng.entities.OpenApiDocument;
import org.tomitribe.tribestream.registryng.security.LoginContext;
import org.tomitribe.tribestream.registryng.service.search.SearchEngine;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceException;
import javax.transaction.Transactional;
import java.io.StringWriter;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;

/**
 * Central access to all OpenAPI documents.
 * Currently simply reads documents from the directory defined by the system property "openapirepo.dir".
 */
@Transactional
@ApplicationScoped
public class Repository {
    private static final Logger LOGGER = Logger.getLogger(Repository.class.getName());

    @PersistenceContext
    private EntityManager em;

    @Inject
    @Tribe
    private ObjectMapper mapper;

    @Inject
    private LoginContext loginContext;

    @Inject
    private SearchEngine searchEngine;

    public Endpoint findEndpointFromHumanReadableMeta(final String humanReadableApplicationName, final String verb,
            final String humanReadableEndpointPath, final String version) {
        try {
            final List<Endpoint> endpoints = ofNullable(version).filter(v -> !v.trim().isEmpty())
                    .map(v -> em.createNamedQuery(Endpoint.Queries.FIND_BY_HUMAN_REDABLE_PATH, Endpoint.class)
                            .setParameter("applicationVersion", version))
                    .orElseGet(() -> em.createNamedQuery(Endpoint.Queries.FIND_BY_HUMAN_REDABLE_PATH_NO_VERSION,
                            Endpoint.class))
                    .setParameter("applicationName", humanReadableApplicationName).setParameter("verb", verb)
                    .setParameter("endpointPath", humanReadableEndpointPath).setMaxResults(2).getResultList();
            if (endpoints.size() > 1) {
                throw new IllegalArgumentException("Conflicting endpoints: " + endpoints);
            }
            if (endpoints.isEmpty()) {
                return null;
            }
            return endpoints.get(0);
        } catch (final NoResultException e) {
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, "Could not find endpoint by {0}/{1}/{2}",
                        new Object[] { humanReadableApplicationName, verb, humanReadableEndpointPath });
            }
            return null;
        }
    }

    public OpenApiDocument findApplicationFromHumanReadableMetadata(final String humanReadableApplicationName,
            final String version) {
        try {
            final List<OpenApiDocument> openApiDocuments = ofNullable(version).filter(v -> !v.trim().isEmpty())
                    .map(v -> em.createNamedQuery(OpenApiDocument.Queries.FIND_BY_HUMAN_REDABLE_NAME,
                            OpenApiDocument.class).setParameter("applicationVersion", version))
                    .orElseGet(() -> em.createNamedQuery(
                            OpenApiDocument.Queries.FIND_BY_HUMAN_REDABLE_NAME_NO_VERSION, OpenApiDocument.class))
                    .setParameter("applicationName", humanReadableApplicationName).setMaxResults(2).getResultList();
            if (openApiDocuments.size() > 1) {
                throw new IllegalArgumentException("Conflicting documents: " + openApiDocuments);
            }
            if (openApiDocuments.isEmpty()) {
                return null;
            }
            return openApiDocuments.get(0);
        } catch (final NoResultException e) {
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, "Could not find endpoint by {0}", humanReadableApplicationName);
            }
            return null;
        }
    }

    public OpenApiDocument findByApplicationId(final String applicationId) throws NoResultException {
        try {
            return em.createNamedQuery(OpenApiDocument.Queries.FIND_BY_APPLICATIONID, OpenApiDocument.class)
                    .setParameter("applicationId", applicationId).getSingleResult();
        } catch (NoResultException e) {
            LOGGER.log(Level.FINE, "Could not find application by id {0}", applicationId);
            return null;
        }
    }

    public OpenApiDocument findByApplicationIdAndRevision(final String applicationid, final int revision) {
        AuditReader auditReader = AuditReaderFactory.get(em);
        OpenApiDocument openApiDocument = auditReader.find(OpenApiDocument.class, applicationid, revision);

        // Resolve the references here, because the Resource implementation will not be able to do that because
        // it is running another transaction
        for (Endpoint endpoint : openApiDocument.getEndpoints()) {
            endpoint.getDocument();
        }

        return openApiDocument;
    }

    public <T> List<HistoryEntry<T>> getRevisions(final Class<T> entityClass, final String id, final int first,
            final int pageSize) throws NoResultException {
        AuditReader auditReader = AuditReaderFactory.get(em);
        AuditQuery query = auditReader.createQuery().forRevisionsOfEntity(entityClass, false, true);
        query.add(AuditEntity.id().eq(id));
        query.addOrder(AuditEntity.revisionNumber().desc());
        query.setFirstResult(first).setMaxResults(pageSize);

        List<Object[]> objects = query.getResultList();
        return objects.stream().map(HistoryEntry<T>::new).collect(toList());
    }

    /**
     * Returns the number of revisions available for the entity with the given id.
     *
     * @param entityClass the Entity class type to use to find revisions
     * @param id          the ID of the entity
     * @return the number of revisions
     */
    public <T> int getNumberOfRevisions(final Class<T> entityClass, final String id) {
        final AuditQuery query = AuditReaderFactory.get(em).createQuery().forRevisionsOfEntity(entityClass, true,
                true);
        query.add(AuditEntity.id().eq(id));
        query.addProjection(AuditEntity.revisionNumber().count());

        return ((Number) query.getSingleResult()).intValue();
    }

    public OpenApiDocument findByApplicationIdWithEndpoints(final String applicationId) {
        try {
            return em.createNamedQuery(OpenApiDocument.Queries.FIND_BY_APPLICATIONID_WITH_ENDPOINTS,
                    OpenApiDocument.class).setParameter("applicationId", applicationId).getSingleResult();
        } catch (NoResultException e) {
            LOGGER.log(Level.FINE, "Could not find application by id {0}", applicationId);
            return null;
        }
    }

    public Endpoint findEndpointByIdAndRevision(final String endpointId, final int revision) {
        final AuditReader auditReader = AuditReaderFactory.get(em);
        final Endpoint endpoint = auditReader.find(Endpoint.class, endpointId, revision);

        // Resolve the application here, because the caller will not be able to, as it will
        // be running in a different transaction
        endpoint.getApplication().getId();

        return endpoint;
    }

    public OpenApiDocument findApplicationByNameAndVersion(final String name, final String version) {
        try {
            return em.createNamedQuery(OpenApiDocument.Queries.FIND_BY_NAME_AND_VERSION, OpenApiDocument.class)
                    .setParameter("name", name).setParameter("version", version).getSingleResult();
        } catch (NoResultException e) {
            LOGGER.log(Level.FINE, "Could not find application by name '{0}' and version '{1}'",
                    new Object[] { name, version });
            return null;
        }
    }

    public List<OpenApiDocument> findAllApplications() {
        return em.createNamedQuery(OpenApiDocument.Queries.FIND_ALL, OpenApiDocument.class).getResultList();
    }

    public List<OpenApiDocument> findAllApplicationsMetadata() {
        List<OpenApiDocument> result = em
                .createNamedQuery(OpenApiDocument.Queries.FIND_ALL_METADATA, OpenApiDocument.class).getResultList();

        return result;
    }

    public List<OpenApiDocument> findAllApplicationsWithEndpoints() {
        return em.createNamedQuery(OpenApiDocument.Queries.FIND_ALL_WITH_ENDPOINTS, OpenApiDocument.class)
                .getResultList();
    }

    public Endpoint findEndpointById(String endpointId) {

        try {
            return em.find(Endpoint.class, endpointId);
        } catch (NoResultException e) {
            LOGGER.log(Level.FINE, "Could not find endpoint by id %s", endpointId);
            // Not really nice, should be an Optional.
            // Forwarding the exception makes the caller only receive an indistinguishable EJBException
            return null;
        }
    }

    public Collection<Endpoint> findAllEndpoints() {
        return em.createNamedQuery(Endpoint.Queries.FIND_ALL_WITH_APPLICATION, Endpoint.class).getResultList();
    }

    public OpenApiDocument insert(final Swagger swagger) {

        final OpenApiDocument document = new OpenApiDocument();
        document.setName(swagger.getInfo().getTitle());
        document.setVersion(swagger.getInfo().getVersion());

        final Swagger clone = createShallowCopy(swagger);
        clone.setPaths(null);
        document.setSwagger(clone);

        final String username = getUser();

        Date now = new Date();
        document.setCreatedAt(now);
        document.setUpdatedAt(now);
        document.setCreatedBy(username);
        document.setUpdatedBy(username);
        em.persist(document);
        try {
            em.flush();
        } catch (PersistenceException e) {
            if (e.getCause() != null && ConstraintViolationException.class.isInstance(e.getCause())) {
                throw new DuplicatedSwaggerException(e);
            } else {
                throw e;
            }
        }

        // Store the endpoints in a separate table
        if (swagger.getPaths() != null) {
            for (Map.Entry<String, Path> stringPathEntry : swagger.getPaths().entrySet()) {
                final String path = stringPathEntry.getKey();
                final Path pathObject = stringPathEntry.getValue();
                for (Map.Entry<HttpMethod, Operation> httpMethodOperationEntry : pathObject.getOperationMap()
                        .entrySet()) {
                    final String verb = httpMethodOperationEntry.getKey().name().toUpperCase();
                    final Operation operation = httpMethodOperationEntry.getValue();

                    Endpoint endpoint = new Endpoint();
                    endpoint.setApplication(document);
                    endpoint.setPath(path);
                    endpoint.setVerb(verb);
                    endpoint.setOperation(operation);

                    em.persist(endpoint);
                    em.flush();
                    searchEngine.indexEndpoint(endpoint);
                }
            }
        } else {
            searchEngine.indexApplication(document);
        }

        return document;
    }

    protected String getUser() {
        return loginContext.getUsername();
    }

    private void cleanup(Endpoint endpoint) {
        // Semoving empty elements from list.
        // Sometimes the json cannot be translated to an known object. For example,
        // the json parser does not know what type of parametert "{}" represents,
        // so it converts the empty json object into null.
        final List<Parameter> parameters = endpoint.getOperation().getParameters();
        if (parameters != null) {
            endpoint.getOperation()
                    .setParameters(parameters.stream().filter(p -> p != null).collect(Collectors.toList()));
        }
    }

    public Endpoint insert(final Endpoint endpoint, final String applicationId) {
        final OpenApiDocument application = findByApplicationId(applicationId);
        endpoint.setApplication(application);
        final Date now = new Date();
        endpoint.setCreatedAt(now);
        endpoint.setUpdatedAt(now);
        endpoint.setCreatedBy(getUser());
        endpoint.setUpdatedBy(getUser());
        cleanup(endpoint);
        application.setUpdatedAt(now);
        application.setUpdatedBy(getUser());
        em.persist(endpoint);

        // Deletes Empty Application from ES, because the Endpoint is going to replace it.
        // Only needed when adding the first Endpoint.
        searchEngine.deleteApplication(application);
        application.setElasticsearchId(null);
        update(application);

        em.flush();
        searchEngine.indexEndpoint(endpoint);

        return endpoint;
    }

    public static Swagger createShallowCopy(Swagger swagger) {
        Swagger result = new Swagger();
        result.setSwagger(swagger.getSwagger());
        result.info(swagger.getInfo());
        result.setHost(swagger.getHost());
        result.setBasePath(swagger.getBasePath());
        result.setSchemes(swagger.getSchemes());
        result.setConsumes(swagger.getConsumes());
        result.setProduces(swagger.getProduces());
        result.setPaths(swagger.getPaths());
        result.setDefinitions(swagger.getDefinitions());
        result.setParameters(swagger.getParameters());
        result.setResponses(swagger.getResponses());
        result.setSecurityDefinitions(swagger.getSecurityDefinitions());
        result.setSecurity(swagger.getSecurity());
        result.setTags(swagger.getTags());
        result.setExternalDocs(swagger.getExternalDocs());
        return result;
    }

    public OpenApiDocument update(final OpenApiDocument document) {
        document.setUpdatedAt(new Date());
        document.setUpdatedBy(loginContext.getUsername());
        if (document.getSwagger() != null) {
            document.setDocument(convertToJson(document.getSwagger()));
        }
        // Only need to reindex when the Application is Empty. The Application is Empty when exists in ES,
        // meaning that the elasticSearchId is not null.
        if (document.getElasticsearchId() != null) {
            searchEngine.indexApplication(document);
        } else {
            document.getEndpoints().stream().forEach(endpoint -> searchEngine.indexEndpoint(endpoint));
        }

        return em.merge(document);
    }

    public Endpoint update(Endpoint endpoint) {
        endpoint.setUpdatedAt(new Date());
        endpoint.setUpdatedBy(loginContext.getUsername());
        if (endpoint.getOperation() != null) {
            cleanup(endpoint);
            endpoint.setDocument(convertToJson(endpoint.getOperation()));
        }
        searchEngine.indexEndpoint(endpoint);
        return em.merge(endpoint);
    }

    private String convertToJson(Object object) {
        try (StringWriter sw = new StringWriter()) {
            mapper.writeValue(sw, object);
            sw.flush();
            return sw.toString();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public boolean deleteApplication(final String applicationId) {
        final OpenApiDocument document = findByApplicationId(applicationId);
        if (document == null) {
            return false;
        } else {
            ofNullable(document.getEndpoints()).ifPresent(e -> e.forEach(searchEngine::deleteEndpoint));
            searchEngine.deleteApplication(document);
            em.remove(document);
            return true;
        }
    }

    public boolean deleteEndpoint(String applicationId, String endpointId) {
        final Endpoint endpoint = findEndpointById(endpointId);
        if (endpoint == null || !applicationId.equals(endpoint.getApplication().getId())) {
            return false;
        } else {
            searchEngine.deleteEndpoint(endpoint);
            em.remove(endpoint);
            em.flush();

            final OpenApiDocument document = findByApplicationIdWithEndpoints(applicationId);
            if (document.getEndpoints().isEmpty()) {
                searchEngine.indexApplication(document);
            }

            return true;
        }
    }

    public Optional<Endpoint> findEndpointByVerbAndPath(final String applicationId, final String verb,
            final String path) {
        try {
            return Optional
                    .of(em.createNamedQuery(Endpoint.Queries.FIND_BY_APPLICATIONID_VERB_AND_PATH, Endpoint.class)
                            .setParameter("applicationId", applicationId).setParameter("verb", verb)
                            .setParameter("path", path).getSingleResult());
        } catch (final NoResultException e) {
            return Optional.empty();
        }
    }
}