org.eclipse.winery.repository.rest.resources.artifacts.GenericArtifactsResource.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.winery.repository.rest.resources.artifacts.GenericArtifactsResource.java

Source

/*******************************************************************************
 * Copyright (c) 2012-2018 Contributors to the Eclipse Foundation
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0
 * which is available at https://www.apache.org/licenses/LICENSE-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
 *******************************************************************************/
package org.eclipse.winery.repository.rest.resources.artifacts;

import java.io.IOException;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.SortedSet;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.eclipse.winery.common.Util;
import org.eclipse.winery.common.ids.Namespace;
import org.eclipse.winery.common.ids.XmlId;
import org.eclipse.winery.common.ids.definitions.ArtifactTemplateId;
import org.eclipse.winery.common.ids.definitions.ArtifactTypeId;
import org.eclipse.winery.common.ids.definitions.DefinitionsChildId;
import org.eclipse.winery.common.ids.definitions.EntityTypeId;
import org.eclipse.winery.common.ids.definitions.NodeTypeId;
import org.eclipse.winery.common.ids.definitions.RelationshipTypeId;
import org.eclipse.winery.common.version.VersionUtils;
import org.eclipse.winery.generators.ia.Generator;
import org.eclipse.winery.model.tosca.TArtifactTemplate;
import org.eclipse.winery.model.tosca.TArtifactType;
import org.eclipse.winery.model.tosca.TDeploymentArtifact;
import org.eclipse.winery.model.tosca.TEntityTemplate.Properties;
import org.eclipse.winery.model.tosca.TImplementationArtifacts.ImplementationArtifact;
import org.eclipse.winery.model.tosca.TInterface;
import org.eclipse.winery.model.tosca.TNodeType;
import org.eclipse.winery.model.tosca.TRelationshipType;
import org.eclipse.winery.model.tosca.constants.QNames;
import org.eclipse.winery.repository.backend.BackendUtils;
import org.eclipse.winery.repository.backend.IRepository;
import org.eclipse.winery.repository.backend.RepositoryFactory;
import org.eclipse.winery.repository.backend.filebased.FileUtils;
import org.eclipse.winery.repository.datatypes.ids.elements.ArtifactTemplateSourceDirectoryId;
import org.eclipse.winery.repository.datatypes.ids.elements.DirectoryId;
import org.eclipse.winery.repository.rest.RestUtils;
import org.eclipse.winery.repository.rest.resources._support.AbstractComponentInstanceResource;
import org.eclipse.winery.repository.rest.resources._support.INodeTemplateResourceOrNodeTypeImplementationResourceOrRelationshipTypeImplementationResource;
import org.eclipse.winery.repository.rest.resources._support.collections.withid.EntityWithIdCollectionResource;
import org.eclipse.winery.repository.rest.resources.apiData.GenerateArtifactApiData;
import org.eclipse.winery.repository.rest.resources.entitytemplates.artifacttemplates.ArtifactTemplateResource;
import org.eclipse.winery.repository.rest.resources.entitytypeimplementations.nodetypeimplementations.NodeTypeImplementationResource;
import org.eclipse.winery.repository.rest.resources.entitytypeimplementations.nodetypeimplementations.NodeTypeImplementationsResource;
import org.eclipse.winery.repository.rest.resources.entitytypeimplementations.relationshiptypeimplementations.RelationshipTypeImplementationResource;
import org.eclipse.winery.repository.rest.resources.entitytypeimplementations.relationshiptypeimplementations.RelationshipTypeImplementationsResource;
import org.eclipse.winery.repository.rest.resources.servicetemplates.topologytemplates.NodeTemplateResource;

import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;
import org.xml.sax.InputSource;

/**
 * Resource handling both deployment and implementation artifacts
 */
public abstract class GenericArtifactsResource<ArtifactResource extends GenericArtifactResource<ArtifactT>, ArtifactT>
        extends EntityWithIdCollectionResource<ArtifactResource, ArtifactT> {

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

    protected final INodeTemplateResourceOrNodeTypeImplementationResourceOrRelationshipTypeImplementationResource resWithNamespace;

    public GenericArtifactsResource(Class<ArtifactResource> entityResourceTClazz, Class<ArtifactT> entityTClazz,
            List<ArtifactT> list,
            INodeTemplateResourceOrNodeTypeImplementationResourceOrRelationshipTypeImplementationResource res) {
        super(entityResourceTClazz, entityTClazz, list,
                GenericArtifactsResource.getAbstractComponentInstanceResource(res));
        this.resWithNamespace = res;
    }

    /**
     * @return TImplementationArtifact | TDeploymentArtifact (XML) | URL of generated IA zip (in case of autoGenerateIA)
     */
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Creates a new implementation/deployment artifact. If an implementation artifact with the same name already exists, it is <em>overridden</em>.")
    @SuppressWarnings("unchecked")
    public Response generateArtifact(GenerateArtifactApiData apiData, @Context UriInfo uriInfo) {
        // we assume that the parent ComponentInstance container exists

        final IRepository repository = RepositoryFactory.getRepository();

        if (StringUtils.isEmpty(apiData.artifactName)) {
            return Response.status(Status.BAD_REQUEST).entity("Empty artifactName").build();
        }
        if (StringUtils.isEmpty(apiData.artifactType)) {
            if (StringUtils.isEmpty(apiData.artifactTemplateName)
                    || StringUtils.isEmpty(apiData.artifactTemplateNamespace)) {
                if (StringUtils.isEmpty(apiData.artifactTemplate)) {
                    return Response.status(Status.BAD_REQUEST)
                            .entity("No artifact type given and no template given. Cannot guess artifact type")
                            .build();
                }
            }
        }

        if (!StringUtils.isEmpty(apiData.autoGenerateIA)) {
            if (StringUtils.isEmpty(apiData.javaPackage)) {
                return Response.status(Status.BAD_REQUEST)
                        .entity("no java package name supplied for IA auto generation.").build();
            }
            if (StringUtils.isEmpty(apiData.interfaceName)) {
                return Response.status(Status.BAD_REQUEST)
                        .entity("no interface name supplied for IA auto generation.").build();
            }
        }

        // convert second calling form to first calling form
        if (!StringUtils.isEmpty(apiData.artifactTemplate)) {
            QName qname = QName.valueOf(apiData.artifactTemplate);
            apiData.artifactTemplateName = qname.getLocalPart();
            apiData.artifactTemplateNamespace = qname.getNamespaceURI();
        }

        Document doc = null;

        // check artifact specific content for validity
        // if invalid, abort and do not create anything
        if (!StringUtils.isEmpty(apiData.artifactSpecificContent)) {
            try {
                DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
                InputSource is = new InputSource();
                StringReader sr = new StringReader(apiData.artifactSpecificContent);
                is.setCharacterStream(sr);
                doc = db.parse(is);
            } catch (Exception e) {
                // FIXME: currently we allow a single element only. However, the content should be internally wrapped by an (arbitrary) XML element as the content will be nested in the artifact element, too
                GenericArtifactsResource.LOGGER.debug("Invalid content", e);
                return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
            }
        }

        // determine artifactTemplate and artifactType

        ArtifactTypeId artifactTypeId;
        ArtifactTemplateId artifactTemplateId = null;
        ArtifactTemplateResource artifactTemplateResource = null;

        boolean doAutoCreateArtifactTemplate = !(StringUtils.isEmpty(apiData.autoCreateArtifactTemplate)
                || apiData.autoCreateArtifactTemplate.equalsIgnoreCase("no")
                || apiData.autoCreateArtifactTemplate.equalsIgnoreCase("false"));
        if (!doAutoCreateArtifactTemplate) {
            // no auto creation
            if (!StringUtils.isEmpty(apiData.artifactTemplateName)
                    && !StringUtils.isEmpty(apiData.artifactTemplateNamespace)) {
                QName artifactTemplateQName = new QName(apiData.artifactTemplateNamespace,
                        apiData.artifactTemplateName);
                artifactTemplateId = BackendUtils.getDefinitionsChildId(ArtifactTemplateId.class,
                        artifactTemplateQName);
            }
            if (StringUtils.isEmpty(apiData.artifactType)) {
                // derive the type from the artifact template
                if (artifactTemplateId == null) {
                    return Response.status(Status.NOT_ACCEPTABLE).entity(
                            "No artifactTemplate and no artifactType provided. Deriving the artifactType is not possible.")
                            .build();
                }
                @NonNull
                final QName type = repository.getElement(artifactTemplateId).getType();
                artifactTypeId = BackendUtils.getDefinitionsChildId(ArtifactTypeId.class, type);
            } else {
                // artifactTypeStr is directly given, use that
                artifactTypeId = BackendUtils.getDefinitionsChildId(ArtifactTypeId.class, apiData.artifactType);
            }
        } else {
            // do the artifact template auto creation magic

            if (StringUtils.isEmpty(apiData.artifactType)) {
                return Response.status(Status.BAD_REQUEST)
                        .entity("Artifact template auto creation requested, but no artifact type supplied.")
                        .build();
            }

            artifactTypeId = BackendUtils.getDefinitionsChildId(ArtifactTypeId.class, apiData.artifactType);
            // ensure that given type exists
            if (!repository.exists(artifactTypeId)) {
                LOGGER.debug("Artifact type {} is created", apiData.artifactType);
                final TArtifactType element = repository.getElement(artifactTypeId);
                try {
                    repository.setElement(artifactTypeId, element);
                } catch (IOException e) {
                    throw new WebApplicationException(e);
                }
            }

            if (StringUtils.isEmpty(apiData.artifactTemplateName)
                    || StringUtils.isEmpty(apiData.artifactTemplateNamespace)) {
                // no explicit name provided
                // we use the artifactNameStr as prefix for the
                // artifact template name

                // we create a new artifact template in the namespace of the parent
                // element
                Namespace namespace = this.resWithNamespace.getNamespace();

                artifactTemplateId = new ArtifactTemplateId(namespace,
                        new XmlId(apiData.artifactName + "artifactTemplate", false));
            } else {
                QName artifactTemplateQName = new QName(apiData.artifactTemplateNamespace,
                        apiData.artifactTemplateName);
                artifactTemplateId = new ArtifactTemplateId(artifactTemplateQName);
            }

            // even if artifactTemplate does not exist, it is loaded
            final TArtifactTemplate artifactTemplate = repository.getElement(artifactTemplateId);
            artifactTemplate.setType(artifactTypeId.getQName());
            try {
                repository.setElement(artifactTemplateId, artifactTemplate);
            } catch (IOException e) {
                throw new WebApplicationException(e);
            }
        }

        // variable artifactTypeId is set
        // variable artifactTemplateId is not null if artifactTemplate has been generated

        // we have to generate the DA/IA resource now
        // Doing it here instead of doing it at the subclasses is dirty on the
        // one hand, but quicker to implement on the other hand

        // Create the artifact itself

        ArtifactT resultingArtifact;

        if (this instanceof ImplementationArtifactsResource) {
            ImplementationArtifact a = new ImplementationArtifact();
            // Winery internal id is the name of the artifact:
            // store the name
            a.setName(apiData.artifactName);
            a.setInterfaceName(apiData.interfaceName);
            a.setOperationName(apiData.operationName);
            assert (artifactTypeId != null);
            a.setArtifactType(artifactTypeId.getQName());
            if (artifactTemplateId != null) {
                a.setArtifactRef(artifactTemplateId.getQName());
            }
            if (doc != null) {
                // the content has been checked for validity at the beginning of the method.
                // If this point in the code is reached, the XML has been parsed into doc
                // just copy over the dom node. Hopefully, that works...
                a.getAny().add(doc.getDocumentElement());
            }

            this.list.add((ArtifactT) a);
            resultingArtifact = (ArtifactT) a;
        } else {
            // for comments see other branch

            TDeploymentArtifact a = new TDeploymentArtifact();
            a.setName(apiData.artifactName);
            assert (artifactTypeId != null);
            a.setArtifactType(artifactTypeId.getQName());
            if (artifactTemplateId != null) {
                a.setArtifactRef(artifactTemplateId.getQName());
            }
            if (doc != null) {
                a.getAny().add(doc.getDocumentElement());
            }

            this.list.add((ArtifactT) a);
            resultingArtifact = (ArtifactT) a;
        }

        // TODO: Check for error, and in case one found return it
        RestUtils.persist(super.res);

        if (StringUtils.isEmpty(apiData.autoGenerateIA)) {
            // No IA generation
            return Response.created(RestUtils.createURI(Util.URLencode(apiData.artifactName)))
                    .entity(resultingArtifact).build();
        } else {
            // after everything was created, we fire up the artifact generation
            return this.generateImplementationArtifact(apiData.interfaceName, apiData.javaPackage, uriInfo,
                    artifactTemplateId);
        }
    }

    /**
     * Generates a unique and valid name to be used for the generated maven project name, java project name, class name,
     * port type name.
     */
    private String generateName(EntityTypeId typeId, String interfaceName) {
        String name = Util.namespaceToJavaPackage(typeId.getNamespace().getDecoded());
        name += Util.FORBIDDEN_CHARACTER_REPLACEMENT;

        // Winery already ensures that this is a valid NCName
        // getName() returns the id of the nodeType: A nodeType carries the "id" attribute only (and no name attribute)
        name += VersionUtils.getNameWithoutVersion(typeId.getXmlId().getDecoded());

        // Two separators to distinguish node type and interface part
        name += Util.FORBIDDEN_CHARACTER_REPLACEMENT;
        name += Util.FORBIDDEN_CHARACTER_REPLACEMENT;
        name += Util.namespaceToJavaPackage(interfaceName);

        // In addition we must replace '.', because Java class names must not
        // contain dots, but for Winery they are fine.
        return name.replace(".", Util.FORBIDDEN_CHARACTER_REPLACEMENT);
    }

    /**
     * Generates the implementation artifact using the implementation artifact generator. Also sets the properties
     * according to the requirements of OpenTOSCA.
     */
    private Response generateImplementationArtifact(String interfaceName, String javaPackage, UriInfo uriInfo,
            ArtifactTemplateId artifactTemplateId) {

        assert (this instanceof ImplementationArtifactsResource);
        IRepository repository = RepositoryFactory.getRepository();

        QName type = RestUtils.getType(this.res);
        EntityTypeId typeId = getTypeId(type).orElseThrow(IllegalStateException::new);
        TInterface i = findInterface(typeId, interfaceName).orElseThrow(IllegalStateException::new);

        Path workingDir;
        try {
            workingDir = Files.createTempDirectory("winery");
        } catch (IOException e2) {
            GenericArtifactsResource.LOGGER.debug("Could not create temporary directory", e2);
            return Response.serverError().entity("Could not create temporary directory").build();
        }

        URI artifactTemplateFilesUri = uriInfo.getBaseUri().resolve(RestUtils.getAbsoluteURL(artifactTemplateId))
                .resolve("files");
        URL artifactTemplateFilesUrl;
        try {
            artifactTemplateFilesUrl = artifactTemplateFilesUri.toURL();
        } catch (MalformedURLException e2) {
            GenericArtifactsResource.LOGGER.debug("Could not convert URI to URL", e2);
            return Response.serverError().entity("Could not convert URI to URL").build();
        }

        String name = this.generateName(typeId, interfaceName);
        Generator gen = new Generator(i, javaPackage, artifactTemplateFilesUrl, name, workingDir.toFile());
        Path targetPath;
        try {
            targetPath = gen.generateProject();
        } catch (Exception e) {
            LOGGER.debug("IA generator failed", e);
            return Response.serverError().entity("IA generator failed").build();
        }

        DirectoryId fileDir = new ArtifactTemplateSourceDirectoryId(artifactTemplateId);
        try {
            BackendUtils.importDirectory(targetPath, repository, fileDir);
        } catch (IOException e) {
            throw new WebApplicationException(e);
        }

        // clean up
        FileUtils.forceDelete(workingDir);

        this.storeProperties(artifactTemplateId, typeId, name);

        URI url = uriInfo.getBaseUri().resolve(Util.getUrlPath(artifactTemplateId));
        return Response.created(url).build();
    }

    private Optional<EntityTypeId> getTypeId(QName type) {
        if (this.res instanceof NodeTypeImplementationResource
                || this.res instanceof NodeTypeImplementationsResource) {
            return Optional.of(new NodeTypeId(type));
        } else if (this.res instanceof RelationshipTypeImplementationResource
                || this.res instanceof RelationshipTypeImplementationsResource) {
            return Optional.of(new RelationshipTypeId(type));
        }
        return Optional.empty();
    }

    private Optional<TInterface> findInterface(EntityTypeId id, String interfaceName) {
        TInterface i = null;
        List<TInterface> interfaces = new ArrayList<>();
        IRepository repository = RepositoryFactory.getRepository();
        if (this.res instanceof NodeTypeImplementationResource
                || this.res instanceof NodeTypeImplementationsResource) {
            TNodeType nodeType = repository.getElement((NodeTypeId) id);
            if (nodeType.getInterfaces() != null) {
                interfaces.addAll(nodeType.getInterfaces().getInterface());
            }
        } else if (this.res instanceof RelationshipTypeImplementationResource
                || this.res instanceof RelationshipTypeImplementationsResource) {
            TRelationshipType relationshipType = repository.getElement((RelationshipTypeId) id);
            if (relationshipType.getSourceInterfaces() != null) {
                interfaces.addAll(relationshipType.getSourceInterfaces().getInterface());
            }
            if (relationshipType.getTargetInterfaces() != null) {
                interfaces.addAll(relationshipType.getTargetInterfaces().getInterface());
            }
            if (relationshipType.getInterfaces() != null) {
                interfaces.addAll(relationshipType.getInterfaces().getInterface());
            }
        }
        Iterator<TInterface> it = interfaces.iterator();
        do {
            i = it.next();
            if (i.getName().equals(interfaceName)) {
                return Optional.of(i);
            }
        } while (it.hasNext());
        return Optional.empty();
    }

    private void storeProperties(ArtifactTemplateId artifactTemplateId, DefinitionsChildId typeId, String name) {
        // We generate the properties by hand instead of using JAX-B as using JAX-B causes issues at org.eclipse.winery.common.ModelUtilities.getPropertiesKV(TEntityTemplate):
        // getAny() does not always return "w3c.dom.element" anymore

        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder;
        try {
            builder = dbf.newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            GenericArtifactsResource.LOGGER.error(e.getMessage(), e);
            return;
        }
        Document doc = builder.newDocument();
        String namespace = QNames.QNAME_ARTIFACT_TYPE_WAR.getNamespaceURI();
        Element root = doc.createElementNS(namespace, "WSProperties");
        doc.appendChild(root);

        Element element = doc.createElementNS(namespace, "ServiceEndpoint");
        Text text = doc.createTextNode("/services/" + name + "Port");
        element.appendChild(text);
        root.appendChild(element);

        element = doc.createElementNS(namespace, "PortType");
        text = doc.createTextNode("{" + typeId.getNamespace().getDecoded() + "}" + name);
        element.appendChild(text);
        root.appendChild(element);

        element = doc.createElementNS(namespace, "InvocationType");
        text = doc.createTextNode("SOAP/HTTP");
        element.appendChild(text);
        root.appendChild(element);

        Properties properties = new Properties();
        properties.setAny(root);

        final IRepository repository = RepositoryFactory.getRepository();
        final TArtifactTemplate artifactTemplate = repository.getElement(artifactTemplateId);
        artifactTemplate.setProperties(properties);
        try {
            repository.setElement(artifactTemplateId, artifactTemplate);
        } catch (IOException e) {
            throw new WebApplicationException(e);
        }
    }

    /**
     * Required for artifacts.jsp
     *
     * @return list of known artifact types.
     */
    public List<QName> getAllArtifactTypes() {
        SortedSet<ArtifactTypeId> allArtifactTypes = RepositoryFactory.getRepository()
                .getAllDefinitionsChildIds(ArtifactTypeId.class);
        List<QName> res = new ArrayList<>(allArtifactTypes.size());
        for (ArtifactTypeId id : allArtifactTypes) {
            res.add(id.getQName());
        }
        return res;
    }

    /**
     * Required for artifacts.jsp
     *
     * @return list of all contained artifacts.
     */
    public abstract Collection<ArtifactResource> getAllArtifactResources();

    /**
     * Required by artifact.jsp to decide whether to display "Deployment Artifact" or "Implementation Artifact"
     */
    public boolean getIsDeploymentArtifacts() {
        return (this instanceof DeploymentArtifactsResource);
    }

    /**
     * required by artifacts.jsp
     */
    public String getNamespace() {
        return this.resWithNamespace.getNamespace().getDecoded();
    }

    /**
     * For saving resources, an AbstractComponentInstanceResource is required. DAs may be attached to a node template,
     * which is not an AbstractComponentInstanceResource, but its grandparent resource ServiceTemplate is
     *
     * @param res the resource to determine the the AbstractComponentInstanceResource for
     * @return the AbstractComponentInstanceResource where the given res is contained in
     */
    public static AbstractComponentInstanceResource getAbstractComponentInstanceResource(
            INodeTemplateResourceOrNodeTypeImplementationResourceOrRelationshipTypeImplementationResource res) {
        final AbstractComponentInstanceResource r;
        if (res instanceof NodeTemplateResource) {
            r = ((NodeTemplateResource) res).getServiceTemplateResource();
        } else {
            // quick hack: the resource has to be an abstract component instance
            r = (AbstractComponentInstanceResource) res;
        }
        return r;
    }
}