aboutsummaryrefslogtreecommitdiffstats
path: root/winery/org.eclipse.winery.repository/src/main/java/org/eclipse/winery/repository/resources/AbstractComponentsResource.java
blob: 5702376aec326d3987cf8b12ecf68a5b453d5a75 (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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
/*******************************************************************************
 * Copyright (c) 2012-2013 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;

import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.SortedSet;

import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
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 javax.xml.namespace.QName;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.winery.common.Util;
import org.eclipse.winery.common.ids.definitions.ArtifactTemplateId;
import org.eclipse.winery.common.ids.definitions.PolicyTemplateId;
import org.eclipse.winery.common.ids.definitions.ServiceTemplateId;
import org.eclipse.winery.common.ids.definitions.TOSCAComponentId;
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.backend.ResourceCreationResult;
import org.eclipse.winery.repository.resources.entitytemplates.artifacttemplates.ArtifactTemplatesResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.sun.jersey.api.NotFoundException;
import com.sun.jersey.api.view.Viewable;

/**
 * Resource handling of a set of components. Each component has to provide a
 * class to handle the set. This is required to provide the correct instances of
 * TOSCAcomponentIds.
 * 
 * TODO: Add generics here!
 * {@link Utils.getComponentIdClassForComponentContainer} is then obsolete
 */
public abstract class AbstractComponentsResource<R extends AbstractComponentInstanceResource> {
	
	protected static final Logger logger = LoggerFactory.getLogger(AbstractComponentsResource.class);
	
	
	@GET
	@Produces(MediaType.TEXT_HTML)
	public Response getHTML() {
		return Response.ok().entity(new Viewable("/jsp/genericcomponentpage.jsp", new GenericComponentPageData(this.getClass()))).build();
	}
	
	/**
	 * Creates a new component instance in the given namespace
	 * 
	 * @param namespace plain namespace
	 * @param id plain id
	 */
	protected ResourceCreationResult onPost(String namespace, String name) {
		ResourceCreationResult res;
		if (StringUtils.isEmpty(namespace) || StringUtils.isEmpty(name)) {
			res = new ResourceCreationResult(Status.BAD_REQUEST);
		} else {
			String id = Utils.createXMLidAsString(name);
			TOSCAComponentId tcId;
			try {
				tcId = this.getTOSCAcomponentId(namespace, id, false);
				res = this.createComponentInstance(tcId);
				// in case the resource additionally supports a name attribute, we set the original name
				if (res.getStatus().equals(Status.CREATED)) {
					if ((tcId instanceof ServiceTemplateId) || (tcId instanceof ArtifactTemplateId) || (tcId instanceof PolicyTemplateId)) {
						// these three types have an additional name (instead of a pure id)
						// we store the name
						IHasName resource = (IHasName) AbstractComponentsResource.getComponentInstaceResource(tcId);
						resource.setName(name);
					}
				}
			} catch (Exception e) {
				AbstractComponentsResource.logger.debug("Could not create id instance", e);
				res = new ResourceCreationResult(Status.INTERNAL_SERVER_ERROR);
			}
		}
		return res;
	}
	
	/**
	 * Creates a new component instance in the given namespace
	 * 
	 * @param namespace plain namespace
	 * @param id plain id
	 * @param ignored this parameter is ignored, but necessary for
	 *            {@link ArtifactTemplatesResource} to be able to accept the
	 *            artifact type at a post
	 */
	@POST
	@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
	@Produces(MediaType.TEXT_PLAIN)
	public Response onPost(@FormParam("namespace") String namespace, @FormParam("name") String name, String ignored) {
		ResourceCreationResult res = this.onPost(namespace, name);
		return res.getResponse();
	}
	
	/**
	 * Creates a TOSCAcomponentId for the given namespace / id combination
	 * 
	 * Uses reflection to create a new instance
	 */
	protected TOSCAComponentId getTOSCAcomponentId(String namespace, String id, boolean URLencoded) throws Exception {
		Class<? extends TOSCAComponentId> idClass = Utils.getComponentIdClassForComponentContainer(this.getClass());
		return BackendUtils.getTOSCAcomponentId(idClass, namespace, id, URLencoded);
	}
	
	/**
	 * Creates a new instance of the current component
	 * 
	 * @return <ul>
	 *         <li>Status.CREATED (201) if the resource has been created,</li>
	 *         <li>Status.CONFLICT if the resource already exists,</li>
	 *         <li>Status.INTERNAL_SERVER_ERROR (500) if something went wrong</li>
	 *         </ul>
	 */
	protected ResourceCreationResult createComponentInstance(TOSCAComponentId tcId) {
		return BackendUtils.create(tcId);
	}
	
	@SuppressWarnings("unchecked")
	private static Class<? extends AbstractComponentInstanceResource> getComponentInstanceResourceClassForType(String type) {
		// Guess the package
		String pkg = "org.eclipse.winery.repository.resources.";
		
		pkg += Utils.getIntermediateLocationStringForType(type, ".");
		
		// naming convention: Instance is named after container, but without the
		// plural s
		String className = pkg + "." + type + "Resource";
		try {
			return (Class<? extends AbstractComponentInstanceResource>) Class.forName(className);
		} catch (ClassNotFoundException e) {
			throw new IllegalStateException("Could not find id class for component instance", e);
		}
	}
	
	/**
	 * 
	 * @param namespace encoded namespace
	 * @param id encoded id
	 * @return an instance of the requested resource
	 */
	@Path("{namespace}/{id}/")
	public R getComponentInstaceResource(@PathParam("namespace") String namespace, @PathParam("id") String id) {
		return this.getComponentInstaceResource(namespace, id, true);
	}
	
	/**
	 * @param encoded specifies whether namespace and id are encoded
	 * @return an instance of the requested resource
	 */
	@SuppressWarnings("unchecked")
	public R getComponentInstaceResource(String namespace, String id, boolean encoded) {
		TOSCAComponentId tcId;
		try {
			tcId = this.getTOSCAcomponentId(namespace, id, encoded);
		} catch (Exception e) {
			throw new IllegalStateException("Could not create id instance", e);
		}
		return (R) AbstractComponentsResource.getComponentInstaceResource(tcId);
	}
	
	/**
	 * @return an instance of the requested resource
	 */
	public AbstractComponentInstanceResource getComponentInstaceResource(QName qname) {
		return this.getComponentInstaceResource(qname.getNamespaceURI(), qname.getLocalPart(), false);
	}
	
	/**
	 * @return an instance of the requested resource
	 * @throws NotFoundException if resource doesn't exist.
	 */
	public static AbstractComponentInstanceResource getComponentInstaceResource(TOSCAComponentId tcId) {
		String type = Util.getTypeForComponentId(tcId.getClass());
		if (!Repository.INSTANCE.exists(tcId)) {
			AbstractComponentsResource.logger.debug("TOSCA component id " + tcId.toString() + " not found");
			throw new NotFoundException("TOSCA component id " + tcId.toString() + " not found");
		}
		Class<? extends AbstractComponentInstanceResource> newResource = AbstractComponentsResource.getComponentInstanceResourceClassForType(type);
		Constructor<?>[] constructors = newResource.getConstructors();
		assert (constructors.length == 1);
		AbstractComponentInstanceResource newInstance;
		try {
			newInstance = (AbstractComponentInstanceResource) constructors[0].newInstance(tcId);
		} catch (InstantiationException | IllegalAccessException
				| IllegalArgumentException | InvocationTargetException e) {
			AbstractComponentsResource.logger.error("Could not instantiate sub resource " + tcId);
			throw new IllegalStateException("Could not instantiate sub resource", e);
		}
		return newInstance;
	}
	
	/**
	 * Returns resources for all known component instances
	 * 
	 * Required by topologytemplateedit.jsp
	 */
	public Collection<AbstractComponentInstanceResource> getAll() {
		Class<? extends TOSCAComponentId> idClass = Utils.getComponentIdClassForComponentContainer(this.getClass());
		SortedSet<? extends TOSCAComponentId> allTOSCAcomponentIds = Repository.INSTANCE.getAllTOSCAComponentIds(idClass);
		ArrayList<AbstractComponentInstanceResource> res = new ArrayList<AbstractComponentInstanceResource>(allTOSCAcomponentIds.size());
		for (TOSCAComponentId id : allTOSCAcomponentIds) {
			AbstractComponentInstanceResource r = AbstractComponentsResource.getComponentInstaceResource(id);
			res.add(r);
		}
		return res;
	}
	
	/**
	 * Used by org.eclipse.winery.repository.repository.client and by the
	 * artifactcreationdialog.tag. Especially the "name" field is used there at
	 * the UI
	 * 
	 * @return A list of all ids of all instances of this component type. If the
	 *         "name" attribute is required, that name is used as id <br />
	 *         Format:
	 *         <code>[({"namespace": "<namespace>", "id": "<id>"},)* ]</code>. A
	 *         <code>name<code> field is added if the model allows an additional name attribute
	 */
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	public String getListOfAllIds() {
		Class<? extends TOSCAComponentId> idClass = Utils.getComponentIdClassForComponentContainer(this.getClass());
		boolean supportsNameAttribute = Util.instanceSupportsNameAttribute(idClass);
		SortedSet<? extends TOSCAComponentId> allTOSCAcomponentIds = Repository.INSTANCE.getAllTOSCAComponentIds(idClass);
		JsonFactory jsonFactory = new JsonFactory();
		StringWriter sw = new StringWriter();
		
		try {
			JsonGenerator jg = jsonFactory.createGenerator(sw);
			// We produce org.eclipse.winery.repository.client.WineryRepositoryClient.NamespaceAndId by hand here
			// Refactoring could move this class to common and fill it here
			jg.writeStartArray();
			for (TOSCAComponentId id : allTOSCAcomponentIds) {
				jg.writeStartObject();
				jg.writeStringField("namespace", id.getNamespace().getDecoded());
				jg.writeStringField("id", id.getXmlId().getDecoded());
				if (supportsNameAttribute) {
					AbstractComponentInstanceResource componentInstaceResource = AbstractComponentsResource.getComponentInstaceResource(id);
					String name = ((IHasName) componentInstaceResource).getName();
					jg.writeStringField("name", name);
				}
				jg.writeEndObject();
			}
			jg.writeEndArray();
			jg.close();
		} catch (Exception e) {
			AbstractComponentsResource.logger.error(e.getMessage(), e);
			return "[]";
		}
		return sw.toString();
	}
	
}