13a8420a9ad3fab561ce19cccccae05f7dd84e93
[vfc/nfvo/wfengine.git] / winery / org.eclipse.winery.repository / src / main / java / org / eclipse / winery / repository / resources / servicetemplates / plans / PlansResource.java
1 /*******************************************************************************
2  * Copyright (c) 2012-2015 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
8  *
9  * Contributors:
10  *     Oliver Kopp - initial API and implementation
11  *******************************************************************************/
12 package org.eclipse.winery.repository.resources.servicetemplates.plans;
13
14 import java.io.IOException;
15 import java.io.InputStream;
16 import java.io.StringWriter;
17 import java.util.List;
18
19 import javax.ws.rs.Consumes;
20 import javax.ws.rs.POST;
21 import javax.ws.rs.Produces;
22 import javax.ws.rs.core.MediaType;
23 import javax.ws.rs.core.Response;
24 import javax.ws.rs.core.Response.Status;
25
26 import org.apache.commons.lang3.StringUtils;
27 import org.eclipse.winery.common.RepositoryFileReference;
28 import org.eclipse.winery.common.Util;
29 import org.eclipse.winery.common.ids.XMLId;
30 import org.eclipse.winery.common.ids.definitions.ServiceTemplateId;
31 import org.eclipse.winery.common.ids.elements.PlanId;
32 import org.eclipse.winery.common.ids.elements.PlansId;
33 import org.eclipse.winery.model.tosca.TPlan;
34 import org.eclipse.winery.model.tosca.TPlan.PlanModelReference;
35 import org.eclipse.winery.repository.Constants;
36 import org.eclipse.winery.repository.Utils;
37 import org.eclipse.winery.repository.backend.BackendUtils;
38 import org.eclipse.winery.repository.backend.Repository;
39 import org.eclipse.winery.repository.resources._support.collections.withid.EntityWithIdCollectionResource;
40 import org.eclipse.winery.repository.resources.admin.types.PlanLanguagesManager;
41 import org.eclipse.winery.repository.resources.admin.types.PlanTypesManager;
42 import org.eclipse.winery.repository.resources.servicetemplates.ServiceTemplateResource;
43 import org.restdoc.annotations.RestDoc;
44 import org.restdoc.annotations.RestDocParam;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 import com.fasterxml.jackson.core.JsonFactory;
49 import com.fasterxml.jackson.core.JsonGenerationException;
50 import com.fasterxml.jackson.core.JsonGenerator;
51 import com.sun.jersey.api.view.Viewable;
52 import com.sun.jersey.core.header.FormDataContentDisposition;
53 import com.sun.jersey.multipart.FormDataBodyPart;
54 import com.sun.jersey.multipart.FormDataParam;
55
56 /**
57  * Presents the plans nested in one Service Template
58  */
59 public class PlansResource extends EntityWithIdCollectionResource<PlanResource, TPlan> {
60         
61         private static final Logger logger = LoggerFactory.getLogger(PlansResource.class);
62         
63         
64         public PlansResource(List<TPlan> plans, ServiceTemplateResource res) {
65                 super(PlanResource.class, TPlan.class, plans, res);
66         }
67         
68         @Override
69         public Viewable getHTML() {
70                 return new Viewable("/jsp/servicetemplates/plans/plans.jsp", new PlansResourceData(this.list));
71         }
72         
73         @POST
74         @RestDoc(methodDescription = "<p>Linked plans are currently not supported. Existing plans with the same id are overwritten</p> <p>@return JSON with .tableData: Array with row data for dataTable</p>")
75         @Consumes({MediaType.MULTIPART_FORM_DATA})
76         @Produces(MediaType.APPLICATION_JSON)
77         // the supertype consumes JSON and XML at org.eclipse.winery.repository.resources._support.collections.EntityCollectionResource.addNewElement(EntityT)
78         // @formatter:off
79         public Response onPost(
80                 @FormDataParam("planName") String name,
81                 @FormDataParam("planType") String type,
82                 @FormDataParam("planLanguage") @RestDocParam(description = "the plan language (e..g, BPMN or BPEL). Full URL.") String language,
83                 @FormDataParam("file") @RestDocParam(description="(optional in the case of BPMN4TOSCA) file containing the plan.") InputStream uploadedInputStream,
84                 @FormDataParam("file") FormDataContentDisposition fileDetail,
85                 @FormDataParam("file") FormDataBodyPart body
86         ) {
87         // @formatter:on
88                 if (StringUtils.isEmpty(name)) {
89                         return Response.status(Status.BAD_REQUEST).entity("planName must be given").build();
90                 }
91                 if (StringUtils.isEmpty(type)) {
92                         return Response.status(Status.BAD_REQUEST).entity("planType must be given").build();
93                 }
94                 if (StringUtils.isEmpty(language)) {
95                         return Response.status(Status.BAD_REQUEST).entity("planLanguage must be given").build();
96                 }
97                 
98                 boolean bpmn4toscaMode = org.eclipse.winery.common.constants.Namespaces.URI_BPMN4TOSCA_20.equals(language);
99                 if (!bpmn4toscaMode) {
100                         if (uploadedInputStream == null) {
101                                 return Response.status(Status.BAD_REQUEST).entity("file must be given").build();
102                         }
103                 }
104                 
105                 // A plan carries both a name and an ID
106                 // To be user-friendly, we create the ID based on the name
107                 // the drawback is, that we do not allow two plans with the same name
108                 // during creation, but allow renaming plans to the same name (as we do
109                 // not allow ID renaming)
110                 String xmlId = Utils.createXMLidAsString(name);
111                 
112                 // BEGIN: Store plan file
113                 
114                 // Determine Id
115                 PlansId plansId = new PlansId((ServiceTemplateId) ((ServiceTemplateResource) this.res).getId());
116                 PlanId planId = new PlanId(plansId, new XMLId(xmlId, false));
117                 // Ensure overwriting
118                 if (Repository.INSTANCE.exists(planId)) {
119                         try {
120                                 Repository.INSTANCE.forceDelete(planId);
121                                 // Quick hack to remove the deleted plan from the plans element
122                                 ((ServiceTemplateResource) this.res).synchronizeReferences();
123                         } catch (IOException e) {
124                                 return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build();
125                         }
126                 }
127                 
128                 String fileName;
129                 if (bpmn4toscaMode) {
130                         fileName = xmlId + Constants.SUFFIX_BPMN4TOSCA;
131                         RepositoryFileReference ref = new RepositoryFileReference(planId, fileName);
132                         try {
133                                 Repository.INSTANCE.putContentToFile(ref, "{}", MediaType.APPLICATION_JSON_TYPE);
134                         } catch (IOException e1) {
135                                 return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not create empty plan. " + e1.getMessage()).build();
136                         }
137                 } else {
138                         // We use the filename also as local file name. Alternatively, we could use the xml id
139                         // With URL encoding, this should not be an issue
140                         fileName = Util.URLencode(fileDetail.getFileName());
141                         
142                         // Really store it
143                         RepositoryFileReference ref = new RepositoryFileReference(planId, fileName);
144                         try {
145                                 Repository.INSTANCE.putContentToFile(ref, uploadedInputStream, body.getMediaType());
146                         } catch (IOException e1) {
147                                 return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not store plan. " + e1.getMessage()).build();
148                         }
149                 }
150                 // END: Store plan file
151                 
152                 TPlan plan = new TPlan();
153                 plan.setId(xmlId);
154                 plan.setName(name);
155                 plan.setPlanType(type);
156                 plan.setPlanLanguage(language);
157                 PlansResource.setPlanModelReference(plan, planId, fileName);
158                 this.list.add(plan);
159                 
160                 // prepare result
161                 JsonFactory jsonFactory = new JsonFactory();
162                 StringWriter sw = new StringWriter();
163                 try {
164                         JsonGenerator jGenerator = jsonFactory.createGenerator(sw);
165                         jGenerator.writeStartObject();
166                         jGenerator.writeFieldName("tableData");
167                         jGenerator.writeStartArray();
168                         jGenerator.writeString(xmlId);
169                         jGenerator.writeString(""); // precondition
170                         jGenerator.writeString(name);
171                         jGenerator.writeString(PlanTypesManager.INSTANCE.getShortName(type));
172                         jGenerator.writeString(PlanLanguagesManager.INSTANCE.getShortName(language));
173                         jGenerator.writeEndArray();
174                         jGenerator.writeEndObject();
175                         jGenerator.close();
176                         sw.close();
177                 } catch (JsonGenerationException e) {
178                         PlansResource.logger.error(e.getMessage(), e);
179                         return Response.serverError().build();
180                 } catch (IOException e) {
181                         PlansResource.logger.error(e.getMessage(), e);
182                         return Response.serverError().build();
183                 }
184                 
185                 Response res = BackendUtils.persist(this.res);
186                 if (res.getStatus() == 204) {
187                         // everything OK, return created
188                         return Response.created(Utils.createURI(Util.URLencode(xmlId))).entity(sw.toString()).build();
189                 } else {
190                         return res;
191                 }
192         }
193         
194         static void setPlanModelReference(TPlan plan, PlanId planId, String fileName) {
195                 PlanModelReference pref = new PlanModelReference();
196                 // Set path relative to Definitions/ path inside CSAR.
197                 pref.setReference("../" + Utils.getURLforPathInsideRepo(BackendUtils.getPathInsideRepo(planId)) + fileName);
198                 plan.setPlanModelReference(pref);
199         }
200         
201         @Override
202         public String getId(TPlan plan) {
203                 return plan.getId();
204         }
205 }