Merge "Inherit from oparent"
[vfc/nfvo/wfengine.git] / winery / org.eclipse.winery.repository / src / main / java / org / eclipse / winery / repository / resources / AbstractComponentsResource.java
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
8  *
9  * Contributors:
10  *     Oliver Kopp - initial API and implementation
11  *******************************************************************************/
12 package org.eclipse.winery.repository.resources;
13
14 import java.io.StringWriter;
15 import java.lang.reflect.Constructor;
16 import java.lang.reflect.InvocationTargetException;
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.SortedSet;
20
21 import javax.ws.rs.Consumes;
22 import javax.ws.rs.FormParam;
23 import javax.ws.rs.GET;
24 import javax.ws.rs.POST;
25 import javax.ws.rs.Path;
26 import javax.ws.rs.PathParam;
27 import javax.ws.rs.Produces;
28 import javax.ws.rs.core.MediaType;
29 import javax.ws.rs.core.Response;
30 import javax.ws.rs.core.Response.Status;
31 import javax.xml.namespace.QName;
32
33 import org.apache.commons.lang3.StringUtils;
34 import org.eclipse.winery.common.Util;
35 import org.eclipse.winery.common.ids.definitions.ArtifactTemplateId;
36 import org.eclipse.winery.common.ids.definitions.PolicyTemplateId;
37 import org.eclipse.winery.common.ids.definitions.ServiceTemplateId;
38 import org.eclipse.winery.common.ids.definitions.TOSCAComponentId;
39 import org.eclipse.winery.repository.Utils;
40 import org.eclipse.winery.repository.backend.BackendUtils;
41 import org.eclipse.winery.repository.backend.Repository;
42 import org.eclipse.winery.repository.backend.ResourceCreationResult;
43 import org.eclipse.winery.repository.resources.entitytemplates.artifacttemplates.ArtifactTemplatesResource;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 import com.fasterxml.jackson.core.JsonFactory;
48 import com.fasterxml.jackson.core.JsonGenerator;
49 import com.sun.jersey.api.NotFoundException;
50 import com.sun.jersey.api.view.Viewable;
51
52 /**
53  * Resource handling of a set of components. Each component has to provide a
54  * class to handle the set. This is required to provide the correct instances of
55  * TOSCAcomponentIds.
56  * 
57  * TODO: Add generics here!
58  * {@link Utils.getComponentIdClassForComponentContainer} is then obsolete
59  */
60 public abstract class AbstractComponentsResource<R extends AbstractComponentInstanceResource> {
61         
62         protected static final Logger logger = LoggerFactory.getLogger(AbstractComponentsResource.class);
63         
64         
65         @GET
66         @Produces(MediaType.TEXT_HTML)
67         public Response getHTML() {
68                 return Response.ok().entity(new Viewable("/jsp/genericcomponentpage.jsp", new GenericComponentPageData(this.getClass()))).build();
69         }
70         
71         /**
72          * Creates a new component instance in the given namespace
73          * 
74          * @param namespace plain namespace
75          * @param id plain id
76          */
77         protected ResourceCreationResult onPost(String namespace, String name) {
78                 ResourceCreationResult res;
79                 if (StringUtils.isEmpty(namespace) || StringUtils.isEmpty(name)) {
80                         res = new ResourceCreationResult(Status.BAD_REQUEST);
81                 } else {
82                         String id = Utils.createXMLidAsString(name);
83                         TOSCAComponentId tcId;
84                         try {
85                                 tcId = this.getTOSCAcomponentId(namespace, id, false);
86                                 res = this.createComponentInstance(tcId);
87                                 // in case the resource additionally supports a name attribute, we set the original name
88                                 if (res.getStatus().equals(Status.CREATED)) {
89                                         if ((tcId instanceof ServiceTemplateId) || (tcId instanceof ArtifactTemplateId) || (tcId instanceof PolicyTemplateId)) {
90                                                 // these three types have an additional name (instead of a pure id)
91                                                 // we store the name
92                                                 IHasName resource = (IHasName) AbstractComponentsResource.getComponentInstaceResource(tcId);
93                                                 resource.setName(name);
94                                         }
95                                 }
96                         } catch (Exception e) {
97                                 AbstractComponentsResource.logger.debug("Could not create id instance", e);
98                                 res = new ResourceCreationResult(Status.INTERNAL_SERVER_ERROR);
99                         }
100                 }
101                 return res;
102         }
103         
104         /**
105          * Creates a new component instance in the given namespace
106          * 
107          * @param namespace plain namespace
108          * @param id plain id
109          * @param ignored this parameter is ignored, but necessary for
110          *            {@link ArtifactTemplatesResource} to be able to accept the
111          *            artifact type at a post
112          */
113         @POST
114         @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
115         @Produces(MediaType.TEXT_PLAIN)
116         public Response onPost(@FormParam("namespace") String namespace, @FormParam("name") String name, String ignored) {
117                 ResourceCreationResult res = this.onPost(namespace, name);
118                 return res.getResponse();
119         }
120         
121         /**
122          * Creates a TOSCAcomponentId for the given namespace / id combination
123          * 
124          * Uses reflection to create a new instance
125          */
126         protected TOSCAComponentId getTOSCAcomponentId(String namespace, String id, boolean URLencoded) throws Exception {
127                 Class<? extends TOSCAComponentId> idClass = Utils.getComponentIdClassForComponentContainer(this.getClass());
128                 return BackendUtils.getTOSCAcomponentId(idClass, namespace, id, URLencoded);
129         }
130         
131         /**
132          * Creates a new instance of the current component
133          * 
134          * @return <ul>
135          *         <li>Status.CREATED (201) if the resource has been created,</li>
136          *         <li>Status.CONFLICT if the resource already exists,</li>
137          *         <li>Status.INTERNAL_SERVER_ERROR (500) if something went wrong</li>
138          *         </ul>
139          */
140         protected ResourceCreationResult createComponentInstance(TOSCAComponentId tcId) {
141                 return BackendUtils.create(tcId);
142         }
143         
144         @SuppressWarnings("unchecked")
145         private static Class<? extends AbstractComponentInstanceResource> getComponentInstanceResourceClassForType(String type) {
146                 // Guess the package
147                 String pkg = "org.eclipse.winery.repository.resources.";
148                 
149                 pkg += Utils.getIntermediateLocationStringForType(type, ".");
150                 
151                 // naming convention: Instance is named after container, but without the
152                 // plural s
153                 String className = pkg + "." + type + "Resource";
154                 try {
155                         return (Class<? extends AbstractComponentInstanceResource>) Class.forName(className);
156                 } catch (ClassNotFoundException e) {
157                         throw new IllegalStateException("Could not find id class for component instance", e);
158                 }
159         }
160         
161         /**
162          * 
163          * @param namespace encoded namespace
164          * @param id encoded id
165          * @return an instance of the requested resource
166          */
167         @Path("{namespace}/{id}/")
168         public R getComponentInstaceResource(@PathParam("namespace") String namespace, @PathParam("id") String id) {
169                 return this.getComponentInstaceResource(namespace, id, true);
170         }
171         
172         /**
173          * @param encoded specifies whether namespace and id are encoded
174          * @return an instance of the requested resource
175          */
176         @SuppressWarnings("unchecked")
177         public R getComponentInstaceResource(String namespace, String id, boolean encoded) {
178                 TOSCAComponentId tcId;
179                 try {
180                         tcId = this.getTOSCAcomponentId(namespace, id, encoded);
181                 } catch (Exception e) {
182                         throw new IllegalStateException("Could not create id instance", e);
183                 }
184                 return (R) AbstractComponentsResource.getComponentInstaceResource(tcId);
185         }
186         
187         /**
188          * @return an instance of the requested resource
189          */
190         public AbstractComponentInstanceResource getComponentInstaceResource(QName qname) {
191                 return this.getComponentInstaceResource(qname.getNamespaceURI(), qname.getLocalPart(), false);
192         }
193         
194         /**
195          * @return an instance of the requested resource
196          * @throws NotFoundException if resource doesn't exist.
197          */
198         public static AbstractComponentInstanceResource getComponentInstaceResource(TOSCAComponentId tcId) {
199                 String type = Util.getTypeForComponentId(tcId.getClass());
200                 if (!Repository.INSTANCE.exists(tcId)) {
201                         AbstractComponentsResource.logger.debug("TOSCA component id " + tcId.toString() + " not found");
202                         throw new NotFoundException("TOSCA component id " + tcId.toString() + " not found");
203                 }
204                 Class<? extends AbstractComponentInstanceResource> newResource = AbstractComponentsResource.getComponentInstanceResourceClassForType(type);
205                 Constructor<?>[] constructors = newResource.getConstructors();
206                 assert (constructors.length == 1);
207                 AbstractComponentInstanceResource newInstance;
208                 try {
209                         newInstance = (AbstractComponentInstanceResource) constructors[0].newInstance(tcId);
210                 } catch (InstantiationException | IllegalAccessException
211                                 | IllegalArgumentException | InvocationTargetException e) {
212                         AbstractComponentsResource.logger.error("Could not instantiate sub resource " + tcId);
213                         throw new IllegalStateException("Could not instantiate sub resource", e);
214                 }
215                 return newInstance;
216         }
217         
218         /**
219          * Returns resources for all known component instances
220          * 
221          * Required by topologytemplateedit.jsp
222          */
223         public Collection<AbstractComponentInstanceResource> getAll() {
224                 Class<? extends TOSCAComponentId> idClass = Utils.getComponentIdClassForComponentContainer(this.getClass());
225                 SortedSet<? extends TOSCAComponentId> allTOSCAcomponentIds = Repository.INSTANCE.getAllTOSCAComponentIds(idClass);
226                 ArrayList<AbstractComponentInstanceResource> res = new ArrayList<AbstractComponentInstanceResource>(allTOSCAcomponentIds.size());
227                 for (TOSCAComponentId id : allTOSCAcomponentIds) {
228                         AbstractComponentInstanceResource r = AbstractComponentsResource.getComponentInstaceResource(id);
229                         res.add(r);
230                 }
231                 return res;
232         }
233         
234         /**
235          * Used by org.eclipse.winery.repository.repository.client and by the
236          * artifactcreationdialog.tag. Especially the "name" field is used there at
237          * the UI
238          * 
239          * @return A list of all ids of all instances of this component type. If the
240          *         "name" attribute is required, that name is used as id <br />
241          *         Format:
242          *         <code>[({"namespace": "<namespace>", "id": "<id>"},)* ]</code>. A
243          *         <code>name<code> field is added if the model allows an additional name attribute
244          */
245         @GET
246         @Produces(MediaType.APPLICATION_JSON)
247         public String getListOfAllIds() {
248                 Class<? extends TOSCAComponentId> idClass = Utils.getComponentIdClassForComponentContainer(this.getClass());
249                 boolean supportsNameAttribute = Util.instanceSupportsNameAttribute(idClass);
250                 SortedSet<? extends TOSCAComponentId> allTOSCAcomponentIds = Repository.INSTANCE.getAllTOSCAComponentIds(idClass);
251                 JsonFactory jsonFactory = new JsonFactory();
252                 StringWriter sw = new StringWriter();
253                 
254                 try {
255                         JsonGenerator jg = jsonFactory.createGenerator(sw);
256                         // We produce org.eclipse.winery.repository.client.WineryRepositoryClient.NamespaceAndId by hand here
257                         // Refactoring could move this class to common and fill it here
258                         jg.writeStartArray();
259                         for (TOSCAComponentId id : allTOSCAcomponentIds) {
260                                 jg.writeStartObject();
261                                 jg.writeStringField("namespace", id.getNamespace().getDecoded());
262                                 jg.writeStringField("id", id.getXmlId().getDecoded());
263                                 if (supportsNameAttribute) {
264                                         AbstractComponentInstanceResource componentInstaceResource = AbstractComponentsResource.getComponentInstaceResource(id);
265                                         String name = ((IHasName) componentInstaceResource).getName();
266                                         jg.writeStringField("name", name);
267                                 }
268                                 jg.writeEndObject();
269                         }
270                         jg.writeEndArray();
271                         jg.close();
272                 } catch (Exception e) {
273                         AbstractComponentsResource.logger.error(e.getMessage(), e);
274                         return "[]";
275                 }
276                 return sw.toString();
277         }
278         
279 }