Add winery source code
[vfc/nfvo/wfengine.git] / winery / org.eclipse.winery.repository.client / src / main / java / org / eclipse / winery / repository / client / WineryRepositoryClient.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.client;
13
14 import java.io.IOException;
15 import java.io.InputStream;
16 import java.net.HttpURLConnection;
17 import java.net.InetSocketAddress;
18 import java.net.Proxy;
19 import java.net.URL;
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.LinkedList;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.SortedSet;
28 import java.util.TreeSet;
29
30 import javax.ws.rs.core.MediaType;
31 import javax.ws.rs.core.MultivaluedMap;
32 import javax.xml.XMLConstants;
33 import javax.xml.bind.JAXBContext;
34 import javax.xml.bind.JAXBException;
35 import javax.xml.bind.Marshaller;
36 import javax.xml.bind.Unmarshaller;
37 import javax.xml.namespace.QName;
38 import javax.xml.parsers.DocumentBuilder;
39 import javax.xml.parsers.DocumentBuilderFactory;
40 import javax.xml.parsers.ParserConfigurationException;
41 import javax.xml.validation.Schema;
42 import javax.xml.validation.SchemaFactory;
43
44 import org.eclipse.winery.common.Util;
45 import org.eclipse.winery.common.beans.NamespaceIdOptionalName;
46 import org.eclipse.winery.common.constants.MimeTypes;
47 import org.eclipse.winery.common.ids.GenericId;
48 import org.eclipse.winery.common.ids.IdUtil;
49 import org.eclipse.winery.common.ids.definitions.TOSCAComponentId;
50 import org.eclipse.winery.common.interfaces.QNameAlreadyExistsException;
51 import org.eclipse.winery.common.interfaces.QNameWithName;
52 import org.eclipse.winery.common.propertydefinitionkv.WinerysPropertiesDefinition;
53 import org.eclipse.winery.model.tosca.TDefinitions;
54 import org.eclipse.winery.model.tosca.TEntityType;
55 import org.eclipse.winery.model.tosca.TExtensibleElements;
56 import org.eclipse.winery.model.tosca.TTopologyTemplate;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59 import org.w3c.dom.Document;
60 import org.xml.sax.SAXException;
61
62 import com.fasterxml.jackson.core.type.TypeReference;
63 import com.fasterxml.jackson.databind.ObjectMapper;
64 import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
65 import com.sun.jersey.api.client.Client;
66 import com.sun.jersey.api.client.ClientResponse;
67 import com.sun.jersey.api.client.WebResource;
68 import com.sun.jersey.api.client.config.ClientConfig;
69 import com.sun.jersey.api.client.config.DefaultClientConfig;
70 import com.sun.jersey.client.urlconnection.HttpURLConnectionFactory;
71 import com.sun.jersey.client.urlconnection.URLConnectionClientHandler;
72 import com.sun.jersey.core.util.MultivaluedMapImpl;
73
74 public final class WineryRepositoryClient implements IWineryRepositoryClient {
75         
76         private static final Logger logger = LoggerFactory.getLogger(WineryRepositoryClient.class);
77         
78         // switch off validation, currently causes more trouble than it brings
79         private static final boolean VALIDATING = false;
80         
81         private final Collection<String> knownURIs = new HashSet<String>();
82         private final Collection<WebResource> repositoryResources = new HashSet<WebResource>();
83         private final Client client;
84         private final ObjectMapper mapper = new ObjectMapper();
85         
86         private final Map<Class<? extends TEntityType>, Map<QName, TEntityType>> entityTypeDataCache;
87         
88         private final Map<GenericId, String> nameCache;
89         private static final int MAX_NAME_CACHE_SIZE = 1000;
90         
91         private String primaryRepository = null;
92         private WebResource primaryWebResource = null;
93         
94         // thread-safe JAXB as inspired by https://jaxb.java.net/guide/Performance_and_thread_safety.html
95         // The other possibility: Each subclass sets JAXBContext.newInstance(theSubClass.class); in its static {} part.
96         // This seems to be more complicated than listing all subclasses in initContext
97         public final static JAXBContext context = WineryRepositoryClient.initContext();
98         
99         // schema aware document builder
100         private final DocumentBuilder toscaDocumentBuilder;
101         
102         
103         // taken from http://stackoverflow.com/a/15253142/873282
104         private static class ConnectionFactory implements HttpURLConnectionFactory {
105                 
106                 Proxy proxy;
107                 
108                 
109                 private void initializeProxy() {
110                         this.proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("localhost", 8888));
111                 }
112                 
113                 @Override
114                 public HttpURLConnection getHttpURLConnection(URL url) throws IOException {
115                         this.initializeProxy();
116                         return (HttpURLConnection) url.openConnection(this.proxy);
117                 }
118         }
119         
120         
121         /**
122          * Creates the client without the use of any proxy
123          */
124         public WineryRepositoryClient() {
125                 this(false);
126         }
127         
128         /**
129          * @param useProxy if a debugging proxy should be used
130          * 
131          * @throws IllegalStateException if DOM parser could not be created
132          */
133         public WineryRepositoryClient(boolean useProxy) {
134                 ClientConfig clientConfig = new DefaultClientConfig();
135                 clientConfig.getClasses().add(JacksonJsonProvider.class);
136                 if (useProxy) {
137                         URLConnectionClientHandler ch = new URLConnectionClientHandler(new ConnectionFactory());
138                         this.client = new Client(ch, clientConfig);
139                 } else {
140                         this.client = Client.create(clientConfig);
141                 }
142                 
143                 this.entityTypeDataCache = new HashMap<>();
144                 this.nameCache = new HashMap<>();
145                 
146                 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
147                 factory.setNamespaceAware(true);
148                 if (WineryRepositoryClient.VALIDATING) {
149                         factory.setValidating(true);
150                         SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
151                         Schema schema;
152                         URL resource = this.getClass().getResource("/TOSCA-v1.0.xsd");
153                         try {
154                                 schema = schemaFactory.newSchema(resource);
155                         } catch (SAXException e) {
156                                 throw new IllegalStateException("Schema could not be initalized", e);
157                         }
158                         factory.setSchema(schema);
159                 }
160                 try {
161                         this.toscaDocumentBuilder = factory.newDocumentBuilder();
162                 } catch (ParserConfigurationException e) {
163                         throw new IllegalStateException("document builder could not be initalized", e);
164                 }
165                 /*
166                  TODO: include this somehow - in the case of VALIDATING
167
168                  Does not work with TTopolgoyTemplate as this is not allowed in the root of an XML document
169                 this.toscaDocumentBuilder.setErrorHandler(new ErrorHandler() {
170
171                         @Override
172                         public void warning(SAXParseException arg0) throws SAXException {
173                                 throw arg0;
174                         }
175
176                         @Override
177                         public void fatalError(SAXParseException arg0) throws SAXException {
178                                 throw arg0;
179                         }
180
181                         @Override
182                         public void error(SAXParseException arg0) throws SAXException {
183                                 throw arg0;
184                         }
185                 });
186                 */
187         }
188         
189         private static JAXBContext initContext() {
190                 // code copied+adapted from JAXBSupport
191                 
192                 JAXBContext context;
193                 try {
194                         // For winery classes, eventually the package+jaxb.index method could be better. See http://stackoverflow.com/a/3628525/873282
195                         // @formatter:off
196                         context = JAXBContext.newInstance(
197                                         TDefinitions.class,
198                                         WinerysPropertiesDefinition.class);
199                         // @formatter:on
200                 } catch (JAXBException e) {
201                         WineryRepositoryClient.logger.error("Could not initialize JAXBContext", e);
202                         throw new IllegalStateException(e);
203                 }
204                 return context;
205         }
206         
207         /**
208          * Creates a marshaller
209          * 
210          * @throws IllegalStateException if marshaller could not be instantiated
211          */
212         private static Marshaller createMarshaller() {
213                 // code copied+adapted from JAXBSupport
214                 Marshaller m;
215                 try {
216                         m = WineryRepositoryClient.context.createMarshaller();
217                         // pretty printed output is required as the XML is sent 1:1 to the browser for editing
218                         m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
219                         // not possible here: m.setProperty("com.sun.xml.bind.namespacePrefixMapper", JAXBSupport.prefixMapper);
220                 } catch (JAXBException e) {
221                         WineryRepositoryClient.logger.error("Could not instantiate marshaller", e);
222                         throw new IllegalStateException(e);
223                 }
224                 return m;
225         }
226         
227         /**
228          * Creates a unmarshaller
229          * 
230          * @throws IllegalStateException if unmarshaller could not be instantiated
231          */
232         private static Unmarshaller createUnmarshaller() {
233                 Unmarshaller um;
234                 try {
235                         um = WineryRepositoryClient.context.createUnmarshaller();
236                 } catch (JAXBException e) {
237                         WineryRepositoryClient.logger.error("Could not instantiate unmarshaller", e);
238                         throw new IllegalStateException(e);
239                 }
240                 return um;
241         }
242         
243         /*** methods directly from IWineryRepositoryClient ***/
244         
245         /**
246          * {@inheritDoc}
247          */
248         @Override
249         public void addRepository(String uri) {
250                 if (this.knownURIs.add(uri)) {
251                         // URI is not already known, add a new resource
252                         WebResource wr = this.client.resource(uri);
253                         this.repositoryResources.add(wr);
254                         if (this.primaryRepository == null) {
255                                 this.primaryRepository = uri;
256                                 this.primaryWebResource = wr;
257                         }
258                 }
259         }
260         
261         /**
262          * {@inheritDoc}
263          */
264         @Override
265         public String getPrimaryRepository() {
266                 return this.primaryRepository;
267         }
268         
269         /**
270          * {@inheritDoc}
271          */
272         @Override
273         public void setPrimaryRepository(String uri) {
274                 this.addRepository(uri);
275                 // now we are sure that a web resource for the uri exists
276                 this.primaryRepository = uri;
277                 // Update the reference to the primaryWebResource
278                 // The appropriate resource has been created via
279                 // this.addRepository(uri);
280                 for (WebResource wr : this.repositoryResources) {
281                         if (wr.getURI().equals(uri)) {
282                                 this.primaryWebResource = wr;
283                                 break;
284                         }
285                 }
286                 assert (this.primaryWebResource != null);
287         }
288         
289         /*** methods directly from IWineryRepository ***/
290         
291         /**
292          * {@inheritDoc}
293          */
294         @Override
295         public SortedSet<String> getNamespaces() {
296                 SortedSet<String> res = new TreeSet<String>();
297                 for (WebResource wr : this.repositoryResources) {
298                         WebResource namespacesResource = wr.path("admin").path("namespaces");
299                         
300                         // this could be parsed using JAXB
301                         // (http://jersey.java.net/nonav/documentation/latest/json.html),
302                         // but we are short in time, so we do a quick hack
303                         String nsList = namespacesResource.accept(MediaType.APPLICATION_JSON).get(String.class);
304                         WineryRepositoryClient.logger.trace(nsList);
305                         List<String> nsListList;
306                         try {
307                                 nsListList = this.mapper.readValue(nsList, new TypeReference<List<String>>() {
308                                 });
309                         } catch (Exception e) {
310                                 WineryRepositoryClient.logger.error(e.getMessage(), e);
311                                 continue;
312                         }
313                         res.addAll(nsListList);
314                 }
315                 return res;
316         }
317         
318         /**
319          * Base method for getQNameListOfAllTypes and getAllTypes.
320          */
321         private <T extends TExtensibleElements> Map<WebResource, List<NamespaceIdOptionalName>> getWRtoNamespaceAndIdListMapOfAllTypes(String path) {
322                 Map<WebResource, List<NamespaceIdOptionalName>> res = new HashMap<WebResource, List<NamespaceIdOptionalName>>();
323                 for (WebResource wr : this.repositoryResources) {
324                         WebResource componentListResource = wr.path(path);
325                         
326                         // this could be parsed using JAXB
327                         // (http://jersey.java.net/nonav/documentation/latest/json.html),
328                         // but we are short in time, so we do a quick hack
329                         // The result also contains the optional name
330                         String idList = componentListResource.accept(MediaType.APPLICATION_JSON).get(String.class);
331                         WineryRepositoryClient.logger.trace(idList);
332                         List<NamespaceIdOptionalName> nsAndIdList;
333                         try {
334                                 nsAndIdList = this.mapper.readValue(idList, new TypeReference<List<NamespaceIdOptionalName>>() {
335                                 });
336                         } catch (Exception e) {
337                                 WineryRepositoryClient.logger.error(e.getMessage(), e);
338                                 continue;
339                         }
340                         res.put(wr, nsAndIdList);
341                 }
342                 return res;
343         }
344         
345         /**
346          * {@inheritDoc}
347          */
348         @Override
349         public String getName(GenericId id) {
350                 if (this.nameCache.containsKey(id)) {
351                         return this.nameCache.get(id);
352                 }
353                 
354                 String name = null;
355                 for (WebResource wr : this.repositoryResources) {
356                         String pathFragment = IdUtil.getURLPathFragment(id);
357                         WebResource resource = wr.path(pathFragment).path("name");
358                         ClientResponse response = resource.accept(MediaType.TEXT_PLAIN_TYPE).get(ClientResponse.class);
359                         if (response.getClientResponseStatus() == ClientResponse.Status.OK) {
360                                 name = response.getEntity(String.class);
361                                 // break loop as the first match is the final result
362                                 break;
363                         }
364                 }
365                 // if all resources did not return "OK", "null" is returned
366                 
367                 if (name != null) {
368                         if (this.nameCache.size() > WineryRepositoryClient.MAX_NAME_CACHE_SIZE) {
369                                 // if cache grew too large, clear it.
370                                 this.nameCache.clear();
371                         }
372                         this.nameCache.put(id, name);
373                 }
374                 
375                 return name;
376         }
377         
378         /**
379          * {@inheritDoc}
380          */
381         @Override
382         public <T extends TEntityType> List<QName> getQNameListOfAllTypes(Class<T> className) {
383                 String path = Util.getURLpathFragmentForCollection(className);
384                 Map<WebResource, List<NamespaceIdOptionalName>> wRtoNamespaceAndIdListMapOfAllTypes = this.getWRtoNamespaceAndIdListMapOfAllTypes(path);
385                 Collection<List<NamespaceIdOptionalName>> namespaceAndIdListCollection = wRtoNamespaceAndIdListMapOfAllTypes.values();
386                 List<QName> res = new ArrayList<QName>(namespaceAndIdListCollection.size());
387                 for (List<NamespaceIdOptionalName> namespaceAndIdList : namespaceAndIdListCollection) {
388                         for (NamespaceIdOptionalName namespaceAndId : namespaceAndIdList) {
389                                 QName qname = new QName(namespaceAndId.getNamespace(), namespaceAndId.getId());
390                                 res.add(qname);
391                         }
392                 }
393                 return res;
394         }
395         
396         /**
397          * Fetches java objects at a given URL
398          * 
399          * @param path the path to use. E.g., "nodetypes" for node types, ...
400          * @param className the class of the expected return type. May be
401          *            TDefinitions or TEntityType. TDefinitions the mode is that the
402          *            import statement are recursively resolved and added to the
403          *            returned Defintitions elment
404          */
405         // we convert an object to T if it T is definitions
406         // does not work without compiler error
407         @SuppressWarnings("unchecked")
408         private <T extends TExtensibleElements> Collection<T> getAllTypes(String path, Class<T> className) {
409                 Map<WebResource, List<NamespaceIdOptionalName>> wRtoNamespaceAndIdListMapOfAllTypes = this.getWRtoNamespaceAndIdListMapOfAllTypes(path);
410                 // now we now all QNames. We have to fetch the full content now
411                 
412                 Collection<T> res = new LinkedList<T>();
413                 for (WebResource wr : wRtoNamespaceAndIdListMapOfAllTypes.keySet()) {
414                         WebResource componentListResource = wr.path(path);
415                         
416                         // go through all ids and fetch detailed information on each
417                         // type
418                         
419                         for (NamespaceIdOptionalName nsAndId : wRtoNamespaceAndIdListMapOfAllTypes.get(wr)) {
420                                 TDefinitions definitions = WineryRepositoryClient.getDefinitions(componentListResource, nsAndId.getNamespace(), nsAndId.getId());
421                                 if (definitions == null) {
422                                         // try next one
423                                         continue;
424                                 }
425                                 
426                                 T result;
427                                 
428                                 if (TDefinitions.class.equals(className)) {
429                                         // mode: complete definitions
430                                         result = (T) definitions;
431                                 } else {
432                                         // mode: only the nested element
433                                         // convention: first element in list is the element we look for
434                                         if (definitions.getServiceTemplateOrNodeTypeOrNodeTypeImplementation().isEmpty()) {
435                                                 result = null;
436                                                 WineryRepositoryClient.logger.error("Type {}/{} was found, but did not return any data", nsAndId.getNamespace(), nsAndId.getId());
437                                         } else {
438                                                 WineryRepositoryClient.logger.trace("Probably found valid data for {}/{}", nsAndId.getNamespace(), nsAndId.getId());
439                                                 result = (T) definitions.getServiceTemplateOrNodeTypeOrNodeTypeImplementation().get(0);
440                                                 
441                                                 this.cache((TEntityType) result, new QName(nsAndId.getNamespace(), nsAndId.getId()));
442                                         }
443                                 }
444                                 
445                                 // TODO: if multiple repositories are used, the new element
446                                 // should be put "sorted" into the list. This could be done by
447                                 // add(parsedResult, index), where index is calculated by
448                                 // incrementing index as long as the current element is smaller
449                                 // than the element to insert.
450                                 if (result != null) {
451                                         res.add(result);
452                                 }
453                         }
454                 }
455                 return res;
456         }
457         
458         /**
459          * Caches the TEntityType data of a QName to avoid multiple get requests
460          * 
461          * NOT thread safe
462          */
463         private void cache(TEntityType et, QName qName) {
464                 Map<QName, TEntityType> map;
465                 if ((map = this.entityTypeDataCache.get(et.getClass())) == null) {
466                         map = new HashMap<>();
467                         this.entityTypeDataCache.put(et.getClass(), map);
468                 } else {
469                         // quick hack to keep cache size small
470                         if (map.size() > 1000) {
471                                 map.clear();
472                         }
473                 }
474                 map.put(qName, et);
475         }
476         
477         private static WebResource getTopologyTemplateWebResource(WebResource base, QName serviceTemplate) {
478                 String nsEncoded = Util.DoubleURLencode(serviceTemplate.getNamespaceURI());
479                 String idEncoded = Util.DoubleURLencode(serviceTemplate.getLocalPart());
480                 WebResource res = base.path("servicetemplates").path(nsEncoded).path(idEncoded).path("topologytemplate");
481                 return res;
482         }
483         
484         /**
485          * {@inheritDoc}
486          */
487         @Override
488         public Collection<QNameWithName> getListOfAllInstances(Class<? extends TOSCAComponentId> clazz) {
489                 // inspired by getQNameListOfAllTypes
490                 String path = Util.getRootPathFragment(clazz);
491                 Map<WebResource, List<NamespaceIdOptionalName>> wRtoNamespaceAndIdListMapOfAllTypes = this.getWRtoNamespaceAndIdListMapOfAllTypes(path);
492                 Collection<List<NamespaceIdOptionalName>> namespaceAndIdListCollection = wRtoNamespaceAndIdListMapOfAllTypes.values();
493                 List<QNameWithName> res = new ArrayList<QNameWithName>(namespaceAndIdListCollection.size());
494                 
495                 for (List<NamespaceIdOptionalName> namespaceAndIdList : namespaceAndIdListCollection) {
496                         for (NamespaceIdOptionalName namespaceAndId : namespaceAndIdList) {
497                                 QNameWithName qn = new QNameWithName();
498                                 qn.qname = new QName(namespaceAndId.getNamespace(), namespaceAndId.getId());
499                                 qn.name = namespaceAndId.getName();
500                                 res.add(qn);
501                         }
502                 }
503                 return res;
504         }
505         
506         /**
507          * {@inheritDoc}
508          */
509         @Override
510         public <T extends TEntityType> Collection<T> getAllTypes(Class<T> c) {
511                 String urlPathFragment = Util.getURLpathFragmentForCollection(c);
512                 Collection<T> allTypes = this.getAllTypes(urlPathFragment, c);
513                 return allTypes;
514         }
515         
516         @Override
517         @SuppressWarnings("unchecked")
518         public <T extends TEntityType> T getType(QName qname, Class<T> type) {
519                 T res = null;
520                 if (this.entityTypeDataCache.containsKey(type)) {
521                         Map<QName, TEntityType> map = this.entityTypeDataCache.get(type);
522                         if (map.containsKey(qname)) {
523                                 res = (T) map.get(qname);
524                         }
525                 }
526                 
527                 if (res == null) {
528                         // not yet seen, try to fetch resource
529                         
530                         for (WebResource wr : this.repositoryResources) {
531                                 String path = Util.getURLpathFragmentForCollection(type);
532                                 
533                                 TDefinitions definitions = WineryRepositoryClient.getDefinitions(wr, path, qname.getNamespaceURI(), qname.getLocalPart());
534                                 
535                                 if (definitions == null) {
536                                         // in case of an error, just try the next one
537                                         continue;
538                                 }
539                                 
540                                 res = (T) definitions.getServiceTemplateOrNodeTypeOrNodeTypeImplementation().get(0);
541                                 this.cache(res, qname);
542                                 break;
543                         }
544                 }
545                 
546                 return res;
547         }
548         
549         /**
550          * Tries to retrieve a TDefinitions from the given resource / encoded(ns) /
551          * encoded(localPart)
552          * 
553          * @return null if 404 or other error
554          */
555         private static TDefinitions getDefinitions(WebResource wr, String path, String ns, String localPart) {
556                 WebResource componentListResource = wr.path(path);
557                 return WineryRepositoryClient.getDefinitions(componentListResource, ns, localPart);
558         }
559         
560         /**
561          * Tries to retrieve a TDefinitions from the given resource / encoded(ns) /
562          * encoded(localPart)
563          * 
564          * @return null if 404 or other error
565          */
566         private static TDefinitions getDefinitions(WebResource componentListResource, String ns, String localPart) {
567                 // we need double encoding as the client decodes the URL once
568                 String nsEncoded = Util.DoubleURLencode(ns);
569                 String idEncoded = Util.DoubleURLencode(localPart);
570                 
571                 WebResource instanceResource = componentListResource.path(nsEncoded).path(idEncoded);
572                 
573                 // TODO: org.eclipse.winery.repository.resources.AbstractComponentInstanceResource.getDefinitionsWithAssociatedThings() could be used to do the resolving at the server
574                 
575                 ClientResponse response = instanceResource.accept(MimeTypes.MIMETYPE_TOSCA_DEFINITIONS).get(ClientResponse.class);
576                 if (response.getStatus() != 200) {
577                         // also handles 404
578                         return null;
579                 }
580                 
581                 TDefinitions definitions;
582                 try {
583                         Unmarshaller um = WineryRepositoryClient.createUnmarshaller();
584                         definitions = (TDefinitions) um.unmarshal(response.getEntityInputStream());
585                 } catch (JAXBException e) {
586                         WineryRepositoryClient.logger.error("Could not umarshal TDefinitions", e);
587                         // try next service
588                         return null;
589                 }
590                 return definitions;
591         }
592         
593         /**
594          * {@inheritDoc}
595          */
596         @Override
597         public <T extends TEntityType> Collection<TDefinitions> getAllTypesWithAssociatedElements(Class<T> c) {
598                 String urlPathFragment = Util.getURLpathFragmentForCollection(c);
599                 Collection<TDefinitions> allTypes = this.getAllTypes(urlPathFragment, TDefinitions.class);
600                 return allTypes;
601         }
602         
603         /**
604          * 
605          * @param stream the stream to parse
606          * @return null if document is invalid
607          */
608         private Document parseAndValidateTOSCAXML(InputStream stream) {
609                 Document document;
610                 try {
611                         document = this.toscaDocumentBuilder.parse(stream);
612                 } catch (SAXException | IOException e) {
613                         WineryRepositoryClient.logger.debug("Could not parse TOSCA file", e);
614                         return null;
615                 }
616                 return document;
617         }
618         
619         /**
620          * {@inheritDoc}
621          */
622         @Override
623         public TTopologyTemplate getTopologyTemplate(QName serviceTemplate) {
624                 // we try all repositories until the first hit
625                 for (WebResource wr : this.repositoryResources) {
626                         WebResource r = WineryRepositoryClient.getTopologyTemplateWebResource(wr, serviceTemplate);
627                         ClientResponse response = r.accept(MediaType.TEXT_XML).get(ClientResponse.class);
628                         if (response.getClientResponseStatus() == ClientResponse.Status.OK) {
629                                 TTopologyTemplate topologyTemplate;
630                                 Document doc = this.parseAndValidateTOSCAXML(response.getEntityInputStream());
631                                 if (doc == null) {
632                                         // no valid document
633                                         return null;
634                                 }
635                                 try {
636                                         topologyTemplate = WineryRepositoryClient.createUnmarshaller().unmarshal(doc.getDocumentElement(), TTopologyTemplate.class).getValue();
637                                 } catch (JAXBException e) {
638                                         WineryRepositoryClient.logger.debug("Could not parse topology, returning null", e);
639                                         return null;
640                                 }
641                                 // first hit: immediately stop and return result
642                                 return topologyTemplate;
643                         }
644                 }
645                 // nothing found
646                 return null;
647         }
648         
649         /**
650          * {@inheritDoc}
651          */
652         @Override
653         public void setTopologyTemplate(QName serviceTemplate, TTopologyTemplate topologyTemplate) throws Exception {
654                 WebResource r = WineryRepositoryClient.getTopologyTemplateWebResource(this.primaryWebResource, serviceTemplate);
655                 String xmlAsString = Util.getXMLAsString(TTopologyTemplate.class, topologyTemplate);
656                 ClientResponse response = r.type(MediaType.TEXT_XML).put(ClientResponse.class, xmlAsString);
657                 WineryRepositoryClient.logger.debug(response.toString());
658                 int status = response.getStatus();
659                 if ((status < 200) || (status >= 300)) {
660                         throw new Exception(response.toString());
661                 }
662         }
663         
664         /**
665          * {@inheritDoc}
666          */
667         @Override
668         public QName getArtifactTypeQNameForExtension(String extension) {
669                 // we try all repositories until the first hit
670                 for (WebResource wr : this.repositoryResources) {
671                         WebResource artifactTypesResource = wr.path("artifacttypes").queryParam("extension", extension);
672                         ClientResponse response = artifactTypesResource.accept(MediaType.TEXT_PLAIN).get(ClientResponse.class);
673                         if (response.getClientResponseStatus() == ClientResponse.Status.OK) {
674                                 QName res = QName.valueOf(response.getEntity(String.class));
675                                 return res;
676                         }
677                 }
678                 return null;
679         }
680         
681         /**
682          * {@inheritDoc}
683          * 
684          * Does NOT check for global QName uniqueness, only in the scope of all
685          * artifact templates
686          */
687         @Override
688         public void createArtifactTemplate(QName qname, QName artifactType) throws QNameAlreadyExistsException {
689                 WebResource artifactTemplates = this.primaryWebResource.path("artifacttemplates");
690                 MultivaluedMap<String, String> map = new MultivaluedMapImpl();
691                 map.putSingle("namespace", qname.getNamespaceURI());
692                 map.putSingle("name", qname.getLocalPart());
693                 map.putSingle("type", artifactType.toString());
694                 ClientResponse response = artifactTemplates.type(MediaType.APPLICATION_FORM_URLENCODED).accept(MediaType.TEXT_PLAIN).post(ClientResponse.class, map);
695                 if (response.getClientResponseStatus() != ClientResponse.Status.CREATED) {
696                         // TODO: pass ClientResponse.Status somehow
697                         // TODO: more fine grained checking for error message. Not all
698                         // failures are that the QName already exists
699                         WineryRepositoryClient.logger.debug(String.format("Error %d when creating id %s from URI %s", response.getStatus(), qname.toString(), this.primaryWebResource.getURI().toString()));
700                         throw new QNameAlreadyExistsException();
701                 }
702                 // no further return is made
703         }
704         
705         /**
706          * {@inheritDoc}
707          */
708         @Override
709         public void createComponent(QName qname, Class<? extends TOSCAComponentId> idClass) throws QNameAlreadyExistsException {
710                 WebResource resource = this.primaryWebResource.path(Util.getRootPathFragment(idClass));
711                 MultivaluedMap<String, String> map = new MultivaluedMapImpl();
712                 map.putSingle("namespace", qname.getNamespaceURI());
713                 map.putSingle("name", qname.getLocalPart());
714                 ClientResponse response = resource.type(MediaType.APPLICATION_FORM_URLENCODED).accept(MediaType.TEXT_PLAIN).post(ClientResponse.class, map);
715                 if (response.getClientResponseStatus() != ClientResponse.Status.CREATED) {
716                         // TODO: pass ClientResponse.Status somehow
717                         // TODO: more fine grained checking for error message. Not all failures are that the QName already exists
718                         WineryRepositoryClient.logger.debug(String.format("Error %d when creating id %s from URI %s", response.getStatus(), qname.toString(), this.primaryWebResource.getURI().toString()));
719                         throw new QNameAlreadyExistsException();
720                 }
721                 // no further return is made
722         }
723         
724         @Override
725         public void forceDelete(GenericId id) throws IOException {
726                 String pathFragment = IdUtil.getURLPathFragment(id);
727                 for (WebResource wr : this.repositoryResources) {
728                         ClientResponse response = wr.path(pathFragment).delete(ClientResponse.class);
729                         if ((response.getClientResponseStatus() != ClientResponse.Status.NO_CONTENT) || (response.getClientResponseStatus() != ClientResponse.Status.NOT_FOUND)) {
730                                 WineryRepositoryClient.logger.debug(String.format("Error %d when deleting id %s from URI %s", response.getStatus(), id.toString(), wr.getURI().toString()));
731                         }
732                 }
733         }
734         
735         @Override
736         public boolean primaryRepositoryAvailable() {
737                 if (this.primaryWebResource == null) {
738                         return false;
739                 }
740                 
741                 ClientResponse response = this.primaryWebResource.get(ClientResponse.class);
742                 boolean res = (response.getClientResponseStatus() == ClientResponse.Status.OK);
743                 return res;
744         }
745         
746 }