summaryrefslogtreecommitdiffstats
path: root/winery/org.eclipse.winery.repository/src/main/java/org/eclipse/winery/repository/resources/servicetemplates/plans/PlansResource.java
blob: 13a8420a9ad3fab561ce19cccccae05f7dd84e93 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/*******************************************************************************
 * Copyright (c) 2012-2015 University of Stuttgart.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and the Apache License 2.0 which both accompany this distribution,
 * and are available at http://www.eclipse.org/legal/epl-v10.html
 * and http://www.apache.org/licenses/LICENSE-2.0
 *
 * Contributors:
 *     Oliver Kopp - initial API and implementation
 *******************************************************************************/
package org.eclipse.winery.repository.resources.servicetemplates.plans;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.List;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.winery.common.RepositoryFileReference;
import org.eclipse.winery.common.Util;
import org.eclipse.winery.common.ids.XMLId;
import org.eclipse.winery.common.ids.definitions.ServiceTemplateId;
import org.eclipse.winery.common.ids.elements.PlanId;
import org.eclipse.winery.common.ids.elements.PlansId;
import org.eclipse.winery.model.tosca.TPlan;
import org.eclipse.winery.model.tosca.TPlan.PlanModelReference;
import org.eclipse.winery.repository.Constants;
import org.eclipse.winery.repository.Utils;
import org.eclipse.winery.repository.backend.BackendUtils;
import org.eclipse.winery.repository.backend.Repository;
import org.eclipse.winery.repository.resources._support.collections.withid.EntityWithIdCollectionResource;
import org.eclipse.winery.repository.resources.admin.types.PlanLanguagesManager;
import org.eclipse.winery.repository.resources.admin.types.PlanTypesManager;
import org.eclipse.winery.repository.resources.servicetemplates.ServiceTemplateResource;
import org.restdoc.annotations.RestDoc;
import org.restdoc.annotations.RestDocParam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.sun.jersey.api.view.Viewable;
import com.sun.jersey.core.header.FormDataContentDisposition;
import com.sun.jersey.multipart.FormDataBodyPart;
import com.sun.jersey.multipart.FormDataParam;

/**
 * Presents the plans nested in one Service Template
 */
public class PlansResource extends EntityWithIdCollectionResource<PlanResource, TPlan> {
	
	private static final Logger logger = LoggerFactory.getLogger(PlansResource.class);
	
	
	public PlansResource(List<TPlan> plans, ServiceTemplateResource res) {
		super(PlanResource.class, TPlan.class, plans, res);
	}
	
	@Override
	public Viewable getHTML() {
		return new Viewable("/jsp/servicetemplates/plans/plans.jsp", new PlansResourceData(this.list));
	}
	
	@POST
	@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>")
	@Consumes({MediaType.MULTIPART_FORM_DATA})
	@Produces(MediaType.APPLICATION_JSON)
	// the supertype consumes JSON and XML at org.eclipse.winery.repository.resources._support.collections.EntityCollectionResource.addNewElement(EntityT)
	// @formatter:off
	public Response onPost(
		@FormDataParam("planName") String name,
		@FormDataParam("planType") String type,
		@FormDataParam("planLanguage") @RestDocParam(description = "the plan language (e..g, BPMN or BPEL). Full URL.") String language,
		@FormDataParam("file") @RestDocParam(description="(optional in the case of BPMN4TOSCA) file containing the plan.") InputStream uploadedInputStream,
		@FormDataParam("file") FormDataContentDisposition fileDetail,
		@FormDataParam("file") FormDataBodyPart body
	) {
	// @formatter:on
		if (StringUtils.isEmpty(name)) {
			return Response.status(Status.BAD_REQUEST).entity("planName must be given").build();
		}
		if (StringUtils.isEmpty(type)) {
			return Response.status(Status.BAD_REQUEST).entity("planType must be given").build();
		}
		if (StringUtils.isEmpty(language)) {
			return Response.status(Status.BAD_REQUEST).entity("planLanguage must be given").build();
		}
		
		boolean bpmn4toscaMode = org.eclipse.winery.common.constants.Namespaces.URI_BPMN4TOSCA_20.equals(language);
		if (!bpmn4toscaMode) {
			if (uploadedInputStream == null) {
				return Response.status(Status.BAD_REQUEST).entity("file must be given").build();
			}
		}
		
		// A plan carries both a name and an ID
		// To be user-friendly, we create the ID based on the name
		// the drawback is, that we do not allow two plans with the same name
		// during creation, but allow renaming plans to the same name (as we do
		// not allow ID renaming)
		String xmlId = Utils.createXMLidAsString(name);
		
		// BEGIN: Store plan file
		
		// Determine Id
		PlansId plansId = new PlansId((ServiceTemplateId) ((ServiceTemplateResource) this.res).getId());
		PlanId planId = new PlanId(plansId, new XMLId(xmlId, false));
		// Ensure overwriting
		if (Repository.INSTANCE.exists(planId)) {
			try {
				Repository.INSTANCE.forceDelete(planId);
				// Quick hack to remove the deleted plan from the plans element
				((ServiceTemplateResource) this.res).synchronizeReferences();
			} catch (IOException e) {
				return Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build();
			}
		}
		
		String fileName;
		if (bpmn4toscaMode) {
			fileName = xmlId + Constants.SUFFIX_BPMN4TOSCA;
			RepositoryFileReference ref = new RepositoryFileReference(planId, fileName);
			try {
				Repository.INSTANCE.putContentToFile(ref, "{}", MediaType.APPLICATION_JSON_TYPE);
			} catch (IOException e1) {
				return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not create empty plan. " + e1.getMessage()).build();
			}
		} else {
			// We use the filename also as local file name. Alternatively, we could use the xml id
			// With URL encoding, this should not be an issue
			fileName = Util.URLencode(fileDetail.getFileName());
			
			// Really store it
			RepositoryFileReference ref = new RepositoryFileReference(planId, fileName);
			try {
				Repository.INSTANCE.putContentToFile(ref, uploadedInputStream, body.getMediaType());
			} catch (IOException e1) {
				return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not store plan. " + e1.getMessage()).build();
			}
		}
		// END: Store plan file
		
		TPlan plan = new TPlan();
		plan.setId(xmlId);
		plan.setName(name);
		plan.setPlanType(type);
		plan.setPlanLanguage(language);
		PlansResource.setPlanModelReference(plan, planId, fileName);
		this.list.add(plan);
		
		// prepare result
		JsonFactory jsonFactory = new JsonFactory();
		StringWriter sw = new StringWriter();
		try {
			JsonGenerator jGenerator = jsonFactory.createGenerator(sw);
			jGenerator.writeStartObject();
			jGenerator.writeFieldName("tableData");
			jGenerator.writeStartArray();
			jGenerator.writeString(xmlId);
			jGenerator.writeString(""); // precondition
			jGenerator.writeString(name);
			jGenerator.writeString(PlanTypesManager.INSTANCE.getShortName(type));
			jGenerator.writeString(PlanLanguagesManager.INSTANCE.getShortName(language));
			jGenerator.writeEndArray();
			jGenerator.writeEndObject();
			jGenerator.close();
			sw.close();
		} catch (JsonGenerationException e) {
			PlansResource.logger.error(e.getMessage(), e);
			return Response.serverError().build();
		} catch (IOException e) {
			PlansResource.logger.error(e.getMessage(), e);
			return Response.serverError().build();
		}
		
		Response res = BackendUtils.persist(this.res);
		if (res.getStatus() == 204) {
			// everything OK, return created
			return Response.created(Utils.createURI(Util.URLencode(xmlId))).entity(sw.toString()).build();
		} else {
			return res;
		}
	}
	
	static void setPlanModelReference(TPlan plan, PlanId planId, String fileName) {
		PlanModelReference pref = new PlanModelReference();
		// Set path relative to Definitions/ path inside CSAR.
		pref.setReference("../" + Utils.getURLforPathInsideRepo(BackendUtils.getPathInsideRepo(planId)) + fileName);
		plan.setPlanModelReference(pref);
	}
	
	@Override
	public String getId(TPlan plan) {
		return plan.getId();
	}
}