1 /*******************************************************************************
2 * Copyright (c) 2012-2013 University of Stuttgart.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * and the Apache License 2.0 which both accompany this distribution,
6 * and are available at http://www.eclipse.org/legal/epl-v10.html
7 * and http://www.apache.org/licenses/LICENSE-2.0
10 * Oliver Kopp - initial API and implementation
11 *******************************************************************************/
12 package org.eclipse.winery.repository.resources.artifacts;
14 import java.io.BufferedInputStream;
16 import java.io.IOException;
17 import java.io.InputStream;
18 import java.io.StringReader;
19 import java.net.MalformedURLException;
22 import java.nio.file.Files;
23 import java.nio.file.Path;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.SortedSet;
30 import javax.ws.rs.Consumes;
31 import javax.ws.rs.FormParam;
32 import javax.ws.rs.POST;
33 import javax.ws.rs.Produces;
34 import javax.ws.rs.core.Context;
35 import javax.ws.rs.core.MediaType;
36 import javax.ws.rs.core.Response;
37 import javax.ws.rs.core.Response.Status;
38 import javax.ws.rs.core.UriInfo;
39 import javax.xml.namespace.QName;
40 import javax.xml.parsers.DocumentBuilder;
41 import javax.xml.parsers.DocumentBuilderFactory;
42 import javax.xml.parsers.ParserConfigurationException;
44 import org.apache.commons.lang3.StringUtils;
45 import org.eclipse.winery.common.RepositoryFileReference;
46 import org.eclipse.winery.common.Util;
47 import org.eclipse.winery.common.ids.Namespace;
48 import org.eclipse.winery.common.ids.XMLId;
49 import org.eclipse.winery.common.ids.definitions.ArtifactTemplateId;
50 import org.eclipse.winery.common.ids.definitions.ArtifactTypeId;
51 import org.eclipse.winery.common.ids.definitions.NodeTypeId;
52 import org.eclipse.winery.common.ids.definitions.TOSCAComponentId;
53 import org.eclipse.winery.generators.ia.Generator;
54 import org.eclipse.winery.model.tosca.TDeploymentArtifact;
55 import org.eclipse.winery.model.tosca.TEntityTemplate.Properties;
56 import org.eclipse.winery.model.tosca.TImplementationArtifacts.ImplementationArtifact;
57 import org.eclipse.winery.model.tosca.TInterface;
58 import org.eclipse.winery.model.tosca.TNodeType;
59 import org.eclipse.winery.repository.Utils;
60 import org.eclipse.winery.repository.backend.BackendUtils;
61 import org.eclipse.winery.repository.backend.Repository;
62 import org.eclipse.winery.repository.backend.ResourceCreationResult;
63 import org.eclipse.winery.repository.backend.filebased.FileUtils;
64 import org.eclipse.winery.repository.datatypes.ids.elements.ArtifactTemplateDirectoryId;
65 import org.eclipse.winery.repository.resources.AbstractComponentInstanceResource;
66 import org.eclipse.winery.repository.resources.AbstractComponentsResource;
67 import org.eclipse.winery.repository.resources.IHasTypeReference;
68 import org.eclipse.winery.repository.resources.INodeTemplateResourceOrNodeTypeImplementationResourceOrRelationshipTypeImplementationResource;
69 import org.eclipse.winery.repository.resources._support.collections.withid.EntityWithIdCollectionResource;
70 import org.eclipse.winery.repository.resources.entitytemplates.PropertiesResource;
71 import org.eclipse.winery.repository.resources.entitytemplates.artifacttemplates.ArtifactTemplateResource;
72 import org.eclipse.winery.repository.resources.entitytypeimplementations.EntityTypeImplementationResource;
73 import org.eclipse.winery.repository.resources.entitytypeimplementations.nodetypeimplementations.NodeTypeImplementationResource;
74 import org.eclipse.winery.repository.resources.entitytypeimplementations.relationshiptypeimplementations.RelationshipTypeImplementationResource;
75 import org.eclipse.winery.repository.resources.entitytypes.nodetypes.NodeTypeResource;
76 import org.eclipse.winery.repository.resources.servicetemplates.topologytemplates.NodeTemplateResource;
77 import org.restdoc.annotations.RestDoc;
78 import org.restdoc.annotations.RestDocParam;
79 import org.slf4j.Logger;
80 import org.slf4j.LoggerFactory;
81 import org.w3c.dom.Document;
82 import org.w3c.dom.Element;
83 import org.w3c.dom.Text;
84 import org.xml.sax.InputSource;
86 import com.sun.jersey.api.view.Viewable;
89 * Resource handling both deployment and implementation artifacts
92 public abstract class GenericArtifactsResource<ArtifactResource extends GenericArtifactResource<ArtifactT>, ArtifactT> extends EntityWithIdCollectionResource<ArtifactResource, ArtifactT> {
94 private static final Logger logger = LoggerFactory.getLogger(GenericArtifactsResource.class);
96 protected final INodeTemplateResourceOrNodeTypeImplementationResourceOrRelationshipTypeImplementationResource resWithNamespace;
99 public GenericArtifactsResource(Class<ArtifactResource> entityResourceTClazz, Class<ArtifactT> entityTClazz, List<ArtifactT> list, INodeTemplateResourceOrNodeTypeImplementationResourceOrRelationshipTypeImplementationResource res) {
100 super(entityResourceTClazz, entityTClazz, list, GenericArtifactsResource.getAbstractComponentInstanceResource(res));
101 this.resWithNamespace = res;
107 * @return TImplementationArtifact | TDeploymentArtifact (XML) | URL of generated IA zip (in case of autoGenerateIA)
110 @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
111 @Produces(MediaType.TEXT_XML)
112 @RestDoc(methodDescription = "Creates a new implementation/deployment artifact. " +
113 "If an implementation artifact with the same name already exists, it is <em>overridden</em>.")
114 @SuppressWarnings("unchecked")
115 public Response onPost(
116 @FormParam("artifactName")
117 @RestDocParam(description = "This is the name of the implementation/deployment artifact. " +
118 "Is <em>also</em>used as prefix of the name of the corresponding artifact template if no specific template is provided. " +
119 "In contrast to CS01, we require a artifactName also for the implementationArtifact to be able to properly referencing it.")
120 String artifactNameStr,
122 @FormParam("artifactTemplate")
123 @RestDocParam(description = "QName of the artifact Template - used by Winery Backend instead of artifactTemplateName + artifactTemplateNS")
124 String artifactTemplate,
126 @FormParam("artifactTemplateName")
127 @RestDocParam(description = "if provided and autoCreateArtifactTemplate, a template of this id localname and artifactTemplateNS generated. " +
128 "Winery always sends this string if auto creation is desired.")
129 String artifactTemplateName,
131 @FormParam("artifactTemplateNS")
132 String artifactTemplateNS,
134 @FormParam("autoCreateArtifactTemplate")
135 @RestDocParam(description = "if empty, no, or false, no artifact template is created. " +
136 "An artifact type has to be given in that case. " +
137 "Furthermore, an artifact template name + artifact template namespace has to be provided. " +
138 "Otherwise, the artifactNameStr is used as name for the artifact and a <em>new</em> artifact template is created having {@code <artifactNameString>Template} as name")
139 String autoCreateArtifactTemplate,
141 @FormParam("artifactType")
142 @RestDocParam(description = "QName of the type, format: {namespace}localname. " +
143 "Optional if artifactTemplateName + artifactTempalteNS is provided")
144 String artifactTypeStr,
146 @FormParam("artifactSpecificContent")
147 @RestDocParam(description = "<em>XML</em> snippet that should be put inside the artifact XML in the TOSCA serialization. " +
148 "This feature will be removed soon. " +
149 "TODO: This only works if there is a single child element expected and not several elements. " +
150 "Future versions of the Winery will support arbitrary content there.")
151 String artifactSpecificContent,
153 @FormParam("interfaceName")
154 String interfaceNameStr,
156 @FormParam("operationName")
157 String operationNameStr,
159 @FormParam("autoGenerateIA")
160 @RestDocParam(description = "If not empty, the IA generator will be called")
161 String autoGenerateIA,
163 @FormParam("javapackage")
164 @RestDocParam(description = "The Java package to use for IA generation")
167 @Context UriInfo uriInfo
169 // we assume that the parent ComponentInstance container exists
173 if (StringUtils.isEmpty(artifactNameStr)) {
174 return Response.status(Status.BAD_REQUEST).entity("Empty artifactName").build();
176 if (StringUtils.isEmpty(artifactTypeStr)) {
177 if (StringUtils.isEmpty(artifactTemplateName) || StringUtils.isEmpty(artifactTemplateNS)) {
178 if (StringUtils.isEmpty(artifactTemplate)) {
179 return Response.status(Status.BAD_REQUEST).entity("No artifact type given and no template given. Cannot guess artifact type").build();
184 if (!StringUtils.isEmpty(autoGenerateIA)) {
185 if (StringUtils.isEmpty(javapackage)) {
186 return Response.status(Status.BAD_REQUEST).entity("no java package name supplied for IA auto generation.").build();
188 if (StringUtils.isEmpty(interfaceNameStr)) {
189 return Response.status(Status.BAD_REQUEST).entity("no interface name supplied for IA auto generation.").build();
193 // convert second calling form to first calling form
194 if (!StringUtils.isEmpty(artifactTemplate)) {
195 QName qname = QName.valueOf(artifactTemplate);
196 artifactTemplateName = qname.getLocalPart();
197 artifactTemplateNS = qname.getNamespaceURI();
202 // check artifact specific content for validity
203 // if invalid, abort and do not create anything
204 if (!StringUtils.isEmpty(artifactSpecificContent)) {
206 DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
207 InputSource is = new InputSource();
208 StringReader sr = new StringReader(artifactSpecificContent);
209 is.setCharacterStream(sr);
211 } catch (Exception e) {
212 // 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
213 GenericArtifactsResource.logger.debug("Invalid content", e);
214 return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
218 // determine artifactTemplate and artifactType
220 ArtifactTypeId artifactTypeId;
221 ArtifactTemplateId artifactTemplateId = null;
222 ArtifactTemplateResource artifactTemplateResource = null;
224 boolean doAutoCreateArtifactTemplate = !(StringUtils.isEmpty(autoCreateArtifactTemplate) || autoCreateArtifactTemplate.equalsIgnoreCase("no") || autoCreateArtifactTemplate.equalsIgnoreCase("false"));
225 if (!doAutoCreateArtifactTemplate) {
227 if (!StringUtils.isEmpty(artifactTemplateName) && !StringUtils.isEmpty(artifactTemplateNS)) {
228 QName artifactTemplateQName = new QName(artifactTemplateNS, artifactTemplateName);
229 artifactTemplateId = BackendUtils.getTOSCAcomponentId(ArtifactTemplateId.class, artifactTemplateQName);
231 if (StringUtils.isEmpty(artifactTypeStr)) {
232 // derive the type from the artifact template
233 if (artifactTemplateId == null) {
234 return Response.status(Status.NOT_ACCEPTABLE).entity("No artifactTemplate and no artifactType provided. Deriving the artifactType is not possible.").build();
236 artifactTemplateResource = new ArtifactTemplateResource(artifactTemplateId);
237 artifactTypeId = BackendUtils.getTOSCAcomponentId(ArtifactTypeId.class, artifactTemplateResource.getType());
239 // artifactTypeStr is directly given, use that
240 artifactTypeId = BackendUtils.getTOSCAcomponentId(ArtifactTypeId.class, artifactTypeStr);
243 // do the artifact template auto creation magic
245 if (StringUtils.isEmpty(artifactTypeStr)) {
246 return Response.status(Status.BAD_REQUEST).entity("Artifact template auto creation requested, but no artifact type supplied.").build();
249 // we assume that the type points to a valid artifact type
250 artifactTypeId = BackendUtils.getTOSCAcomponentId(ArtifactTypeId.class, artifactTypeStr);
252 if (StringUtils.isEmpty(artifactTemplateName) || StringUtils.isEmpty(artifactTemplateNS)) {
253 // no explicit name provided
254 // we use the artifactNameStr as prefix for the
255 // artifact template name
257 // we create a new artifact template in the namespace of the parent
259 Namespace namespace = this.resWithNamespace.getNamespace();
261 artifactTemplateId = new ArtifactTemplateId(namespace, new XMLId(artifactNameStr + "artifactTemplate", false));
263 QName artifactTemplateQName = new QName(artifactTemplateNS, artifactTemplateName);
264 artifactTemplateId = new ArtifactTemplateId(artifactTemplateQName);
266 ResourceCreationResult creationResult = BackendUtils.create(artifactTemplateId);
267 if (!creationResult.isSuccess()) {
268 // something went wrong. skip
269 return creationResult.getResponse();
272 // associate the type to the created artifact template
273 artifactTemplateResource = new ArtifactTemplateResource(artifactTemplateId);
274 // set the type. The resource is automatically persisted inside
275 artifactTemplateResource.setType(artifactTypeStr);
278 // variable artifactTypeId is set
279 // variable artifactTemplateId is not null if artifactTemplate has been generated
281 // we have to generate the DA/IA resource now
282 // Doing it here instead of doing it at the subclasses is dirty on the
283 // one hand, but quicker to implement on the other hand
285 // Create the artifact itself
287 ArtifactT resultingArtifact;
289 if (this instanceof ImplementationArtifactsResource) {
290 ImplementationArtifact a = new ImplementationArtifact();
291 // Winery internal id is the name of the artifact:
293 a.setName(artifactNameStr);
294 a.setInterfaceName(interfaceNameStr);
295 a.setOperationName(operationNameStr);
296 assert (artifactTypeId != null);
297 a.setArtifactType(artifactTypeId.getQName());
298 if (artifactTemplateId != null) {
299 a.setArtifactRef(artifactTemplateId.getQName());
302 // the content has been checked for validity at the beginning of the method.
303 // If this point in the code is reached, the XML has been parsed into doc
304 // just copy over the dom node. Hopefully, that works...
305 a.getAny().add(doc.getDocumentElement());
308 this.list.add((ArtifactT) a);
309 resultingArtifact = (ArtifactT) a;
311 // for comments see other branch
313 TDeploymentArtifact a = new TDeploymentArtifact();
314 a.setName(artifactNameStr);
315 assert (artifactTypeId != null);
316 a.setArtifactType(artifactTypeId.getQName());
317 if (artifactTemplateId != null) {
318 a.setArtifactRef(artifactTemplateId.getQName());
321 a.getAny().add(doc.getDocumentElement());
324 this.list.add((ArtifactT) a);
325 resultingArtifact = (ArtifactT) a;
328 Response persistResponse = BackendUtils.persist(super.res);
329 // TODO: check for error and in case one found return that
331 if (StringUtils.isEmpty(autoGenerateIA)) {
333 // we include an XML for the data table
335 String implOrDeplArtifactXML = Utils.getXMLAsString(resultingArtifact);
337 return Response.created(Utils.createURI(Util.URLencode(artifactNameStr))).entity(implOrDeplArtifactXML).build();
339 // after everything was created, we fire up the artifact generation
340 return this.generateImplementationArtifact(interfaceNameStr, javapackage, uriInfo, artifactTemplateId, artifactTemplateResource);
345 * Generates a unique and valid name to be used for the generated maven
346 * project name, java project name, class name, port type name.
348 private String generateName(NodeTypeId nodeTypeId, String interfaceName) {
349 String name = Util.namespaceToJavaPackage(nodeTypeId.getNamespace().getDecoded());
350 name += Util.FORBIDDEN_CHARACTER_REPLACEMENT;
352 // Winery already ensures that this is a valid NCName
353 // getName() returns the id of the nodeType: A nodeType carries the "id" attribute only (and no name attribute)
354 name += nodeTypeId.getXmlId().getDecoded();
356 // Two separators to distinguish node type and interface part
357 name += Util.FORBIDDEN_CHARACTER_REPLACEMENT;
358 name += Util.FORBIDDEN_CHARACTER_REPLACEMENT;
359 name += Util.namespaceToJavaPackage(interfaceName);
361 // In addition we must replace '.', because Java class names must not
362 // contain dots, but for Winery they are fine.
363 return name.replace(".", Util.FORBIDDEN_CHARACTER_REPLACEMENT);
367 * Generates the implementation artifact using the implementation artifact
368 * generator. Also sets the proeprties according to the requirements of
371 * @param interfaceNameStr
374 * @param artifactTemplateId
375 * @param artifactTemplateResource the resource associated with the
376 * artifactTempalteId. If null, the object is created in this
379 * @return {@inheritDoc}
381 private Response generateImplementationArtifact(String interfaceNameStr, String javapackage, UriInfo uriInfo, ArtifactTemplateId artifactTemplateId, ArtifactTemplateResource artifactTemplateResource) {
384 assert (this instanceof ImplementationArtifactsResource);
385 IHasTypeReference typeRes = (EntityTypeImplementationResource) this.res;
386 QName type = typeRes.getType();
387 TOSCAComponentId typeId;
388 TNodeType nodeType = null;
389 if (typeRes instanceof NodeTypeImplementationResource) {
390 // TODO: refactor: This is more a model/repo utilities thing than something which should happen here...
392 typeId = new NodeTypeId(type);
393 NodeTypeResource ntRes = (NodeTypeResource) AbstractComponentsResource.getComponentInstaceResource(typeId);
395 // required for IA Generation
396 nodeType = ntRes.getNodeType();
398 List<TInterface> interfaces = nodeType.getInterfaces().getInterface();
399 Iterator<TInterface> it = interfaces.iterator();
402 if (iface.getName().equals(interfaceNameStr)) {
405 } while (it.hasNext());
406 // iface now contains the right interface
408 assert (typeRes instanceof RelationshipTypeImplementationResource);
409 return Response.serverError().entity("IA creation for relation ship type implementations not yet possible").build();
414 workingDir = Files.createTempDirectory("winery");
415 } catch (IOException e2) {
416 GenericArtifactsResource.logger.debug("Could not create temporary directory", e2);
417 return Response.serverError().entity("Could not create temporary directory").build();
420 URI artifactTemplateFilesUri = uriInfo.getBaseUri().resolve(Utils.getAbsoluteURL(artifactTemplateId)).resolve("files/");
421 URL artifactTemplateFilesUrl;
423 artifactTemplateFilesUrl = artifactTemplateFilesUri.toURL();
424 } catch (MalformedURLException e2) {
425 GenericArtifactsResource.logger.debug("Could not convert URI to URL", e2);
426 return Response.serverError().entity("Could not convert URI to URL").build();
429 String name = this.generateName((NodeTypeId) typeId, interfaceNameStr);
430 Generator gen = new Generator(iface, javapackage, artifactTemplateFilesUrl, name, workingDir.toFile());
431 File zipFile = gen.generateProject();
432 if (zipFile == null) {
433 return Response.serverError().entity("IA generator failed").build();
437 // TODO: refactor: this is more a RepositoryUtils thing than a special thing here; see also importFile at CSARImporter
439 ArtifactTemplateDirectoryId fileDir = new ArtifactTemplateDirectoryId(artifactTemplateId);
440 RepositoryFileReference fref = new RepositoryFileReference(fileDir, zipFile.getName().toString());
441 try (InputStream is = Files.newInputStream(zipFile.toPath());
442 BufferedInputStream bis = new BufferedInputStream(is)) {
443 String mediaType = Utils.getMimeType(bis, zipFile.getName());
444 // TODO: do the catch thing as in CSARImporter
446 Repository.INSTANCE.putContentToFile(fref, bis, MediaType.valueOf(mediaType));
447 } catch (IOException e1) {
448 throw new IllegalStateException("Could not import generated files", e1);
453 FileUtils.forceDelete(workingDir);
454 } catch (IOException e) {
455 GenericArtifactsResource.logger.debug("Could not delete working directory", e);
458 // store the properties in the artifact template
459 if (artifactTemplateResource == null) {
460 artifactTemplateResource = (ArtifactTemplateResource) AbstractComponentsResource.getComponentInstaceResource(artifactTemplateId);
462 this.storeProperties(artifactTemplateResource, typeId, name);
464 URI url = uriInfo.getBaseUri().resolve(Utils.getAbsoluteURL(fref));
465 return Response.created(url).build();
469 private final String NS_OPENTOSCA_WAR_TYPE = "http://www.uni-stuttgart.de/opentosca";
472 private void storeProperties(ArtifactTemplateResource artifactTemplateResource, TOSCAComponentId typeId, String name) {
473 // 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):
474 // getAny() does not always return "w3c.dom.element" anymore
476 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
477 DocumentBuilder builder;
479 builder = dbf.newDocumentBuilder();
480 } catch (ParserConfigurationException e) {
481 GenericArtifactsResource.logger.error(e.getMessage(), e);
484 Document doc = builder.newDocument();
485 Element root = doc.createElementNS(this.NS_OPENTOSCA_WAR_TYPE, "WSProperties");
486 doc.appendChild(root);
488 Element element = doc.createElementNS(this.NS_OPENTOSCA_WAR_TYPE, "ServiceEndpoint");
489 Text text = doc.createTextNode("/services/" + name + "Port");
490 element.appendChild(text);
491 root.appendChild(element);
493 element = doc.createElementNS(this.NS_OPENTOSCA_WAR_TYPE, "PortType");
494 text = doc.createTextNode("{" + typeId.getNamespace().getDecoded() + "}" + name);
495 element.appendChild(text);
496 root.appendChild(element);
498 element = doc.createElementNS(this.NS_OPENTOSCA_WAR_TYPE, "InvocationType");
499 text = doc.createTextNode("SOAP/HTTP");
500 element.appendChild(text);
501 root.appendChild(element);
503 Properties properties = new Properties();
504 properties.setAny(root);
505 PropertiesResource propertiesResource = artifactTemplateResource.getPropertiesResource();
506 propertiesResource.setProperties(properties);
510 public Viewable getHTML() {
511 return new Viewable("/jsp/artifacts/artifacts.jsp", this);
515 * Required for artifacts.jsp
517 * @return list of known artifact types.
519 public List<QName> getAllArtifactTypes() {
520 SortedSet<ArtifactTypeId> allArtifactTypes = Repository.INSTANCE.getAllTOSCAComponentIds(ArtifactTypeId.class);
521 List<QName> res = new ArrayList<QName>(allArtifactTypes.size());
522 for (ArtifactTypeId id : allArtifactTypes) {
523 res.add(id.getQName());
529 * Required for artifacts.jsp
531 * @return list of all contained artifacts.
533 public abstract Collection<ArtifactResource> getAllArtifactResources();
536 * Required by artifact.jsp to decide whether to display
537 * "Deployment Artifact" or "Implementation Artifact"
539 public boolean getIsDeploymentArtifacts() {
540 boolean res = (this instanceof DeploymentArtifactsResource);
545 * required by artifacts.jsp
547 public String getNamespace() {
548 return this.resWithNamespace.getNamespace().getDecoded();
552 * For saving resources, an AbstractComponentInstanceResource is required.
553 * DAs may be attached to a node template, which is not an
554 * AbstractComponentInstanceResource, but its grandparent resource
557 * @param res the resource to determine the the
558 * AbstractComponentInstanceResource for
559 * @return the AbstractComponentInstanceResource where the given res is
562 public static AbstractComponentInstanceResource getAbstractComponentInstanceResource(INodeTemplateResourceOrNodeTypeImplementationResourceOrRelationshipTypeImplementationResource res) {
563 final AbstractComponentInstanceResource r;
564 if (res instanceof NodeTemplateResource) {
565 r = ((NodeTemplateResource) res).getServiceTemplateResource();
567 // quick hack: the resource has to be an abstract component instance
568 r = (AbstractComponentInstanceResource) res;