Add winery source code
[vfc/nfvo/wfengine.git] / winery / org.eclipse.winery.repository / src / main / java / org / eclipse / winery / repository / export / CSARExporter.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  *     Kálmán Képes - initial API and implementation and/or initial documentation
11  *     Oliver Kopp - adapted to new storage model and to TOSCA v1.0
12  *******************************************************************************/
13 package org.eclipse.winery.repository.export;
14
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.io.OutputStream;
18 import java.io.PrintWriter;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.HashMap;
22 import java.util.Map;
23
24 import javax.xml.bind.JAXBException;
25 import javax.xml.stream.XMLStreamException;
26 import javax.xml.transform.Transformer;
27 import javax.xml.transform.TransformerConfigurationException;
28 import javax.xml.transform.TransformerException;
29 import javax.xml.transform.TransformerFactory;
30 import javax.xml.transform.dom.DOMSource;
31 import javax.xml.transform.stream.StreamResult;
32
33 import org.apache.commons.compress.archivers.ArchiveEntry;
34 import org.apache.commons.compress.archivers.ArchiveException;
35 import org.apache.commons.compress.archivers.ArchiveOutputStream;
36 import org.apache.commons.compress.archivers.ArchiveStreamFactory;
37 import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
38 import org.apache.commons.configuration.Configuration;
39 import org.apache.commons.configuration.ConfigurationException;
40 import org.apache.commons.configuration.PropertiesConfiguration;
41 import org.apache.commons.io.IOUtils;
42 import org.eclipse.winery.common.RepositoryFileReference;
43 import org.eclipse.winery.common.Util;
44 import org.eclipse.winery.common.constants.MimeTypes;
45 import org.eclipse.winery.common.ids.definitions.ServiceTemplateId;
46 import org.eclipse.winery.common.ids.definitions.TOSCAComponentId;
47 import org.eclipse.winery.model.selfservice.Application;
48 import org.eclipse.winery.model.selfservice.Application.Options;
49 import org.eclipse.winery.model.selfservice.ApplicationOption;
50 import org.eclipse.winery.repository.Constants;
51 import org.eclipse.winery.repository.Prefs;
52 import org.eclipse.winery.repository.backend.Repository;
53 import org.eclipse.winery.repository.datatypes.ids.admin.NamespacesId;
54 import org.eclipse.winery.repository.datatypes.ids.elements.SelfServiceMetaDataId;
55 import org.eclipse.winery.repository.resources.admin.NamespacesResource;
56 import org.eclipse.winery.repository.resources.servicetemplates.selfserviceportal.SelfServicePortalResource;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59 import org.w3c.dom.Document;
60
61 /**
62  * This class exports a CSAR crawling from the the given GenericId<br/>
63  * Currently, only ServiceTemplates are supported<br />
64  * commons-compress is used as an output stream should be provided. An
65  * alternative implementation is to use Java7's Zip File System Provider
66  */
67 public class CSARExporter {
68         
69         public static final String PATH_TO_NAMESPACES_PROPERTIES = "winery/Namespaces.properties";
70         
71         private static final Logger logger = LoggerFactory.getLogger(CSARExporter.class);
72         
73         private static final String DEFINITONS_PATH_PREFIX = "Definitions/";
74         
75         
76         /**
77          * Returns a unique name for the given definitions to be used as filename
78          */
79         private static String getDefinitionsName(TOSCAComponentId id) {
80                 // the prefix is globally unique and the id locally in a namespace
81                 // therefore a concatenation of both is also unique
82                 String res = NamespacesResource.getPrefix(id.getNamespace()) + "__" + id.getXmlId().getEncoded();
83                 return res;
84         }
85         
86         public static String getDefinitionsFileName(TOSCAComponentId id) {
87                 return CSARExporter.getDefinitionsName(id) + Constants.SUFFIX_TOSCA_DEFINITIONS;
88         }
89         
90         public static String getDefinitionsPathInsideCSAR(TOSCAComponentId id) {
91                 return CSARExporter.DEFINITONS_PATH_PREFIX + CSARExporter.getDefinitionsFileName(id);
92         }
93         
94         /**
95          * Writes a complete CSAR containing all necessary things reachable from the
96          * given service template
97          * 
98          * @param id the id of the service template to export
99          * @param out the outputstream to write to
100          * @throws JAXBException
101          */
102         public void writeCSAR(TOSCAComponentId entryId, OutputStream out) throws ArchiveException, IOException, XMLStreamException, JAXBException {
103                 CSARExporter.logger.trace("Starting CSAR export with {}", entryId.toString());
104                 
105                 Map<RepositoryFileReference, String> refMap = new HashMap<RepositoryFileReference, String>();
106                 Collection<String> definitionNames = new ArrayList<>();
107                 
108                 final ArchiveOutputStream zos = new ArchiveStreamFactory().createArchiveOutputStream("zip", out);
109                 
110                 TOSCAExportUtil exporter = new TOSCAExportUtil();
111                 Map<String, Object> conf = new HashMap<>();
112                 
113                 ExportedState exportedState = new ExportedState();
114                 
115                 TOSCAComponentId currentId = entryId;
116                 do {
117                         String defName = CSARExporter.getDefinitionsPathInsideCSAR(currentId);
118                         definitionNames.add(defName);
119                         
120                         zos.putArchiveEntry(new ZipArchiveEntry(defName));
121                         Collection<TOSCAComponentId> referencedIds;
122                         try {
123                                 referencedIds = exporter.exportTOSCA(currentId, zos, refMap, conf);
124                         } catch (IllegalStateException e) {
125                                 // thrown if something went wrong inside the repo
126                                 out.close();
127                                 // we just rethrow as there currently is no error stream.
128                                 throw e;
129                         }
130                         zos.closeArchiveEntry();
131                         
132                         exportedState.flagAsExported(currentId);
133                         exportedState.flagAsExportRequired(referencedIds);
134                         
135                         currentId = exportedState.pop();
136                 } while (currentId != null);
137                 
138                 // if we export a ServiceTemplate, data for the self-service portal might exist
139                 if (entryId instanceof ServiceTemplateId) {
140                         this.addSelfServiceMetaData((ServiceTemplateId) entryId, refMap);
141                 }
142                 
143                 // now, refMap contains all files to be added to the CSAR
144                 
145                 // write manifest directly after the definitions to have it more at the beginning of the ZIP rather than having it at the very end
146                 this.addManifest(entryId, definitionNames, refMap, zos);
147                 
148                 // used for generated XSD schemas
149                 TransformerFactory tFactory = TransformerFactory.newInstance();
150                 Transformer transformer;
151                 try {
152                         transformer = tFactory.newTransformer();
153                 } catch (TransformerConfigurationException e1) {
154                         CSARExporter.logger.debug(e1.getMessage(), e1);
155                         throw new IllegalStateException("Could not instantiate transformer", e1);
156                 }
157                 
158                 // write all referenced files
159                 for (RepositoryFileReference ref : refMap.keySet()) {
160                         String archivePath = refMap.get(ref);
161                         CSARExporter.logger.trace("Creating {}", archivePath);
162                         ArchiveEntry archiveEntry = new ZipArchiveEntry(archivePath);
163                         zos.putArchiveEntry(archiveEntry);
164                         if (ref instanceof DummyRepositoryFileReferenceForGeneratedXSD) {
165                                 CSARExporter.logger.trace("Special treatment for generated XSDs");
166                                 Document document = ((DummyRepositoryFileReferenceForGeneratedXSD) ref).getDocument();
167                                 DOMSource source = new DOMSource(document);
168                                 StreamResult result = new StreamResult(zos);
169                                 try {
170                                         transformer.transform(source, result);
171                                 } catch (TransformerException e) {
172                                         CSARExporter.logger.debug("Could not serialize generated xsd", e);
173                                 }
174                         } else {
175                                 try (InputStream is = Repository.INSTANCE.newInputStream(ref)) {
176                                         IOUtils.copy(is, zos);
177                                 } catch (Exception e) {
178                                         CSARExporter.logger.error("Could not copy file content to ZIP outputstream", e);
179                                 }
180                         }
181                         zos.closeArchiveEntry();
182                 }
183                 
184                 this.addNamespacePrefixes(zos);
185                 
186                 zos.finish();
187                 zos.close();
188         }
189         
190         /**
191          * Writes the configured mapping namespaceprefix -> namespace to the archive
192          * 
193          * This is kind of a quick hack. TODO: during the import, the prefixes
194          * should be extracted using JAXB and stored in the NamespacesResource
195          * 
196          * @throws IOException
197          */
198         private void addNamespacePrefixes(ArchiveOutputStream zos) throws IOException {
199                 Configuration configuration = Repository.INSTANCE.getConfiguration(new NamespacesId());
200                 if (configuration instanceof PropertiesConfiguration) {
201                         // Quick hack: direct serialization only works for PropertiesConfiguration
202                         PropertiesConfiguration pconf = (PropertiesConfiguration) configuration;
203                         ArchiveEntry archiveEntry = new ZipArchiveEntry(CSARExporter.PATH_TO_NAMESPACES_PROPERTIES);
204                         zos.putArchiveEntry(archiveEntry);
205                         try {
206                                 pconf.save(zos);
207                         } catch (ConfigurationException e) {
208                                 CSARExporter.logger.debug(e.getMessage(), e);
209                                 zos.write("#Could not export properties".getBytes());
210                                 zos.write(("#" + e.getMessage()).getBytes());
211                         }
212                         zos.closeArchiveEntry();
213                 }
214         }
215         
216         private void addSelfServiceMetaData(ServiceTemplateId entryId, Map<RepositoryFileReference, String> refMap) {
217                 SelfServiceMetaDataId id = new SelfServiceMetaDataId(entryId);
218                 if (Repository.INSTANCE.exists(id)) {
219                         SelfServicePortalResource res = new SelfServicePortalResource(entryId);
220                         
221                         refMap.put(res.data_xml_ref, Constants.DIRNAME_SELF_SERVICE_METADATA + "/" + "data.xml");
222                         
223                         // The schema says that the images have to exist
224                         // However, at a quick modeling, there might be no images
225                         // Therefore, we check for existence
226                         if (Repository.INSTANCE.exists(res.icon_jpg_ref)) {
227                                 refMap.put(res.icon_jpg_ref, Constants.DIRNAME_SELF_SERVICE_METADATA + "/" + "icon.jpg");
228                         }
229                         if (Repository.INSTANCE.exists(res.image_jpg_ref)) {
230                                 refMap.put(res.image_jpg_ref, Constants.DIRNAME_SELF_SERVICE_METADATA + "/" + "image.jpg");
231                         }
232                         
233                         Application application = res.getApplication();
234                         Options options = application.getOptions();
235                         if (options != null) {
236                                 for (ApplicationOption option : options.getOption()) {
237                                         String url = option.getIconUrl();
238                                         if (Util.isRelativeURI(url)) {
239                                                 RepositoryFileReference ref = new RepositoryFileReference(id, url);
240                                                 if (Repository.INSTANCE.exists(ref)) {
241                                                         refMap.put(ref, Constants.DIRNAME_SELF_SERVICE_METADATA + "/" + url);
242                                                 } else {
243                                                         CSARExporter.logger.error("Data corrupt: pointing to non-existent file " + ref);
244                                                 }
245                                         }
246                                         
247                                         url = option.getPlanInputMessageUrl();
248                                         if (Util.isRelativeURI(url)) {
249                                                 RepositoryFileReference ref = new RepositoryFileReference(id, url);
250                                                 if (Repository.INSTANCE.exists(ref)) {
251                                                         refMap.put(ref, Constants.DIRNAME_SELF_SERVICE_METADATA + "/" + url);
252                                                 } else {
253                                                         CSARExporter.logger.error("Data corrupt: pointing to non-existent file " + ref);
254                                                 }
255                                         }
256                                 }
257                         }
258                 }
259         }
260         
261         private void addManifest(TOSCAComponentId id, Collection<String> definitionNames, Map<RepositoryFileReference, String> refMap, ArchiveOutputStream out) throws IOException {
262                 String entryDefinitionsReference = CSARExporter.getDefinitionsPathInsideCSAR(id);
263                 
264                 out.putArchiveEntry(new ZipArchiveEntry("TOSCA-Metadata/TOSCA.meta"));
265                 PrintWriter pw = new PrintWriter(out);
266                 // Setting Versions
267                 pw.println("TOSCA-Meta-Version: 1.0");
268                 pw.println("CSAR-Version: 1.0");
269                 String versionString = "Created-By: Winery " + Prefs.INSTANCE.getVersion();
270                 pw.println(versionString);
271                 // Winery currently is unaware of tDefinitions, therefore, we use the
272                 // name of the service template
273                 pw.println("Entry-Definitions: " + entryDefinitionsReference);
274                 pw.println();
275                 
276                 assert (definitionNames.contains(entryDefinitionsReference));
277                 for (String name : definitionNames) {
278                         pw.println("Name: " + name);
279                         pw.println("Content-Type: " + org.eclipse.winery.common.constants.MimeTypes.MIMETYPE_TOSCA_DEFINITIONS);
280                         pw.println();
281                 }
282                 
283                 // Setting other files, mainly files belonging to artifacts
284                 for (RepositoryFileReference ref : refMap.keySet()) {
285                         String archivePath = refMap.get(ref);
286                         pw.println("Name: " + archivePath);
287                         String mimeType;
288                         if (ref instanceof DummyRepositoryFileReferenceForGeneratedXSD) {
289                                 mimeType = MimeTypes.MIMETYPE_XSD;
290                         } else {
291                                 mimeType = Repository.INSTANCE.getMimeType(ref);
292                         }
293                         pw.println("Content-Type: " + mimeType);
294                         pw.println();
295                 }
296                 pw.flush();
297                 out.closeArchiveEntry();
298         }
299 }