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
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;
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;
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;
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;
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
67 public class CSARExporter {
69 public static final String PATH_TO_NAMESPACES_PROPERTIES = "winery/Namespaces.properties";
71 private static final Logger logger = LoggerFactory.getLogger(CSARExporter.class);
73 private static final String DEFINITONS_PATH_PREFIX = "Definitions/";
77 * Returns a unique name for the given definitions to be used as filename
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();
86 public static String getDefinitionsFileName(TOSCAComponentId id) {
87 return CSARExporter.getDefinitionsName(id) + Constants.SUFFIX_TOSCA_DEFINITIONS;
90 public static String getDefinitionsPathInsideCSAR(TOSCAComponentId id) {
91 return CSARExporter.DEFINITONS_PATH_PREFIX + CSARExporter.getDefinitionsFileName(id);
95 * Writes a complete CSAR containing all necessary things reachable from the
96 * given service template
98 * @param id the id of the service template to export
99 * @param out the outputstream to write to
100 * @throws JAXBException
102 public void writeCSAR(TOSCAComponentId entryId, OutputStream out) throws ArchiveException, IOException, XMLStreamException, JAXBException {
103 CSARExporter.logger.trace("Starting CSAR export with {}", entryId.toString());
105 Map<RepositoryFileReference, String> refMap = new HashMap<RepositoryFileReference, String>();
106 Collection<String> definitionNames = new ArrayList<>();
108 final ArchiveOutputStream zos = new ArchiveStreamFactory().createArchiveOutputStream("zip", out);
110 TOSCAExportUtil exporter = new TOSCAExportUtil();
111 Map<String, Object> conf = new HashMap<>();
113 ExportedState exportedState = new ExportedState();
115 TOSCAComponentId currentId = entryId;
117 String defName = CSARExporter.getDefinitionsPathInsideCSAR(currentId);
118 definitionNames.add(defName);
120 zos.putArchiveEntry(new ZipArchiveEntry(defName));
121 Collection<TOSCAComponentId> referencedIds;
123 referencedIds = exporter.exportTOSCA(currentId, zos, refMap, conf);
124 } catch (IllegalStateException e) {
125 // thrown if something went wrong inside the repo
127 // we just rethrow as there currently is no error stream.
130 zos.closeArchiveEntry();
132 exportedState.flagAsExported(currentId);
133 exportedState.flagAsExportRequired(referencedIds);
135 currentId = exportedState.pop();
136 } while (currentId != null);
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);
143 // now, refMap contains all files to be added to the CSAR
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);
148 // used for generated XSD schemas
149 TransformerFactory tFactory = TransformerFactory.newInstance();
150 Transformer transformer;
152 transformer = tFactory.newTransformer();
153 } catch (TransformerConfigurationException e1) {
154 CSARExporter.logger.debug(e1.getMessage(), e1);
155 throw new IllegalStateException("Could not instantiate transformer", e1);
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);
170 transformer.transform(source, result);
171 } catch (TransformerException e) {
172 CSARExporter.logger.debug("Could not serialize generated xsd", e);
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);
181 zos.closeArchiveEntry();
184 this.addNamespacePrefixes(zos);
191 * Writes the configured mapping namespaceprefix -> namespace to the archive
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
196 * @throws IOException
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);
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());
212 zos.closeArchiveEntry();
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);
221 refMap.put(res.data_xml_ref, Constants.DIRNAME_SELF_SERVICE_METADATA + "/" + "data.xml");
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");
229 if (Repository.INSTANCE.exists(res.image_jpg_ref)) {
230 refMap.put(res.image_jpg_ref, Constants.DIRNAME_SELF_SERVICE_METADATA + "/" + "image.jpg");
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);
243 CSARExporter.logger.error("Data corrupt: pointing to non-existent file " + ref);
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);
253 CSARExporter.logger.error("Data corrupt: pointing to non-existent file " + ref);
261 private void addManifest(TOSCAComponentId id, Collection<String> definitionNames, Map<RepositoryFileReference, String> refMap, ArchiveOutputStream out) throws IOException {
262 String entryDefinitionsReference = CSARExporter.getDefinitionsPathInsideCSAR(id);
264 out.putArchiveEntry(new ZipArchiveEntry("TOSCA-Metadata/TOSCA.meta"));
265 PrintWriter pw = new PrintWriter(out);
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);
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);
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);
288 if (ref instanceof DummyRepositoryFileReferenceForGeneratedXSD) {
289 mimeType = MimeTypes.MIMETYPE_XSD;
291 mimeType = Repository.INSTANCE.getMimeType(ref);
293 pw.println("Content-Type: " + mimeType);
297 out.closeArchiveEntry();