1 /*******************************************************************************
2 * Copyright (c) 2013,2015 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 * Tobias Binz - initial API and implementation
11 *******************************************************************************/
12 package org.eclipse.winery.generators.ia;
15 import java.io.FileInputStream;
16 import java.io.FileOutputStream;
17 import java.io.IOException;
18 import java.io.InputStream;
20 import java.nio.charset.Charset;
21 import java.nio.file.Files;
22 import java.nio.file.OpenOption;
23 import java.nio.file.Path;
24 import java.nio.file.StandardCopyOption;
25 import java.nio.file.StandardOpenOption;
26 import java.util.ArrayList;
27 import java.util.Enumeration;
28 import java.util.List;
29 import java.util.jar.JarEntry;
30 import java.util.jar.JarFile;
32 import org.apache.commons.compress.archivers.ArchiveEntry;
33 import org.apache.commons.compress.archivers.ArchiveOutputStream;
34 import org.apache.commons.compress.archivers.ArchiveStreamFactory;
35 import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
36 import org.apache.commons.compress.utils.IOUtils;
37 import org.apache.commons.io.FileUtils;
38 import org.eclipse.winery.model.tosca.TBoolean;
39 import org.eclipse.winery.model.tosca.TInterface;
40 import org.eclipse.winery.model.tosca.TOperation;
41 import org.eclipse.winery.model.tosca.TParameter;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
45 public class Generator {
47 private static final Logger logger = LoggerFactory.getLogger(Generator.class);
49 // Placeholder applicable for all files
50 public static final String PLACEHOLDER_JAVA_PACKAGE = "IA_PACKAGE";
51 public static final String PLACEHOLDER_NAMESPACE = "IA_NAMESPACE";
52 public static final String PLACEHOLDER_CLASS_NAME = "IA_CLASS_NAME";
53 public static final String PLACEHOLDER_IA_ARTIFACT_TEMPLATE_UPLOAD_URL = "IA_ARTIFACT_TEMPLATE_UPLOAD_URL";
55 // Placeholders in Java Service Files
56 public static final String PLACEHOLDER_GENERATED_WEBSERVICE_METHODS = "GENERATED_WEBSERVICE_METHODS";
58 // Template folder relative to resources folder in this project
59 public static final String TEMPLATE_PROJECT_FOLDER = "template/project";
60 public static final String TEMPLATE_JAVA_FOLDER = "template/java";
62 private static final String TEMPLATE_JAVA_ABSTRACT_IA_SERVICE = "AbstractIAService.java.template";
63 private static final String TEMPLATE_JAVA_TEMPLATE_SERVICE = "TemplateService.java.template";
65 private final TInterface tinterface;
66 private final File workingDir;
67 private final File outDir;
68 private final String name;
69 private final String javaPackage;
70 private final String namespace;
71 private final URL iaArtifactTemplateUploadUrl;
75 * Creates a new IA Generator instance for the given {@link TInterface}.
77 * @param tinterface TOSCA interface to generate the IA for
78 * @param packageAndNamespace Package to be used for the generated Java
79 * code, e.g. 'org.opentosca.ia'. To generate the respective
80 * Namespace for the Web Service the components of the package
81 * are reverted, prepended with 'http://' and appended with '/'.
82 * This is provided by the user in a textfield in the Winery UI.
83 * @param iaArtifactTemplateUploadUrl The URL to which the generated IA
85 * @param name unique and valid name to be used for the generated maven
86 * project name, java project name, class name, port type name.
87 * @param workingDir working directory to generate the files. This directory
88 * also will contain the ZIP file with the Eclipse project after
91 public Generator(TInterface tinterface, String packageAndNamespace, URL iaArtifactTemplateUploadUrl, String name, File workingDir) {
93 this.tinterface = tinterface;
94 this.javaPackage = packageAndNamespace;
95 this.iaArtifactTemplateUploadUrl = iaArtifactTemplateUploadUrl;
97 this.workingDir = new File(workingDir.getAbsolutePath() + File.separator + this.name);
98 this.outDir = new File(workingDir.getAbsolutePath());
100 if (this.workingDir.exists()) {
101 Generator.logger.error("Workdir " + this.workingDir + " already exits. This might lead to corrupted results if it is not empty!");
104 // Generate Namespace
105 String[] splitPkg = this.javaPackage.split("\\.");
106 String tmpNamespace = "http://";
107 for (int i = splitPkg.length - 1; i >= 0; i--) {
108 tmpNamespace += splitPkg[i];
109 // Add '.' if it is not the last iterations
114 this.namespace = tmpNamespace += "/";
118 * Generates the IA project.
120 * @return The ZIP file containing the maven/eclipse project to be
121 * downloaded by the user.
123 public File generateProject() {
126 Path workingDirPath = this.workingDir.toPath();
127 Files.createDirectories(workingDirPath);
129 // directory to store the template files to generate the java files from
130 Path javaTemplateDir = workingDirPath.resolve("../java");
131 Files.createDirectories(javaTemplateDir);
133 // Copy template project and template java files
134 String s = this.getClass().getResource("").getPath();
135 if (s.contains("jar!")) {
136 Generator.logger.trace("we work on a jar file");
137 Generator.logger.trace("Location of the current class: {}", s);
139 // we have a jar file
140 // format: file:/location...jar!...path-in-the-jar
141 // we only want to have location :)
142 int excl = s.lastIndexOf("!");
143 s = s.substring(0, excl);
144 s = s.substring("file:".length());
146 try (JarFile jf = new JarFile(s);) {
147 Enumeration<JarEntry> entries = jf.entries();
148 while (entries.hasMoreElements()) {
149 JarEntry je = entries.nextElement();
150 String name = je.getName();
151 if (name.startsWith(Generator.TEMPLATE_PROJECT_FOLDER + "/") && (name.length() > (Generator.TEMPLATE_PROJECT_FOLDER.length() + 1))) {
152 // strip "template/" from the beginning to have paths without "template" starting relatively from the working dir
153 name = name.substring(Generator.TEMPLATE_PROJECT_FOLDER.length() + 1);
154 if (je.isDirectory()) {
156 Path dir = workingDirPath.resolve(name);
157 Files.createDirectory(dir);
159 Path file = workingDirPath.resolve(name);
160 try (InputStream is = jf.getInputStream(je);) {
161 Files.copy(is, file, StandardCopyOption.REPLACE_EXISTING);
164 } else if (name.startsWith(Generator.TEMPLATE_JAVA_FOLDER + "/") && (name.length() > (Generator.TEMPLATE_JAVA_FOLDER.length() + 1))) {
165 if (!je.isDirectory()) {
166 // we copy the file directly into javaTemplateDir
167 File f = new File(name);
168 Path file = javaTemplateDir.resolve(f.getName());
169 try (InputStream is = jf.getInputStream(je);) {
170 Files.copy(is, file, StandardCopyOption.REPLACE_EXISTING);
177 // we're running in debug mode, we can work on the plain file system
178 File templateProjectDir = new File(this.getClass().getResource("/" + Generator.TEMPLATE_PROJECT_FOLDER).getFile());
179 FileUtils.copyDirectory(templateProjectDir, this.workingDir);
181 File javaTemplatesDir = new File(this.getClass().getResource("/" + Generator.TEMPLATE_JAVA_FOLDER).getFile());
182 FileUtils.copyDirectory(javaTemplatesDir, javaTemplateDir.toFile());
185 // Create Java Code Folder
186 String[] splitPkg = this.javaPackage.split("\\.");
187 String javaFolderString = this.workingDir.getAbsolutePath() + File.separator + "src" + File.separator + "main" + File.separator + "java";
188 for (int i = 0; i < splitPkg.length; i++) {
189 javaFolderString += File.separator + splitPkg[i];
192 // Copy TEMPLATE_JAVA_ABSTRACT_IA_SERVICE
193 Path templateAbstractIAService = javaTemplateDir.resolve(Generator.TEMPLATE_JAVA_ABSTRACT_IA_SERVICE);
194 File javaAbstractIAService = new File(javaFolderString + File.separator + "AbstractIAService.java");
195 Files.createDirectories(javaAbstractIAService.toPath().getParent());
196 Files.copy(templateAbstractIAService, javaAbstractIAService.toPath(), StandardCopyOption.REPLACE_EXISTING);
198 // Copy and rename TEMPLATE_JAVA_TEMPLATE_SERVICE
199 Path templateJavaService = javaTemplateDir.resolve(Generator.TEMPLATE_JAVA_TEMPLATE_SERVICE);
200 File javaService = new File(javaFolderString + File.separator + this.name + ".java");
201 Files.createDirectories(javaService.toPath().getParent());
202 Files.copy(templateJavaService, javaService.toPath(), StandardCopyOption.REPLACE_EXISTING);
204 this.generateJavaFile(javaService);
205 this.updateFilesRecursively(this.workingDir);
206 return this.packageProject();
208 } catch (Exception e) {
214 private void generateJavaFile(File javaService) throws IOException {
217 StringBuilder sb = new StringBuilder();
219 for (TOperation op : this.tinterface.getOperation()) {
221 sb.append("\t@WebMethod\n");
222 sb.append("\t@SOAPBinding\n");
223 sb.append("\t@Oneway\n");
226 String operationReturn = "void";
227 sb.append("\tpublic " + operationReturn + " " + op.getName() + "(\n");
230 boolean first = true;
231 if (op.getInputParameters() != null) {
232 for (TParameter parameter : op.getInputParameters().getInputParameter()) {
233 String parameterName = parameter.getName();
239 sb.append(",\n\t\t");
242 // Generate @WebParam
243 sb.append("@WebParam(name=\"" + parameterName + "\", targetNamespace=\"" + this.namespace + "\") ");
245 // Handle required and optional parameters using @XmlElement
246 if (parameter.getRequired().equals(TBoolean.YES)) {
247 sb.append("@XmlElement(required=true)");
249 sb.append("@XmlElement(required=false)");
252 sb.append(" String " + parameterName);
255 sb.append("\n\t) {\n");
257 // If there are output parameters we generate the respective HashMap
258 boolean outputParamsExist = (op.getOutputParameters() != null) && (!op.getOutputParameters().getOutputParameter().isEmpty());
259 if (outputParamsExist) {
260 sb.append("\t\t// This HashMap holds the return parameters of this operation.\n");
261 sb.append("\t\tfinal HashMap<String,String> returnParameters = new HashMap<String, String>();\n\n");
264 sb.append("\t\t// TODO: Implement your operation here.\n");
266 // Generate code to set output parameters
267 if (outputParamsExist) {
268 for (TParameter outputParam : op.getOutputParameters().getOutputParameter()) {
269 sb.append("\n\n\t\t// Output Parameter '" + outputParam.getName() + "' ");
270 if (outputParam.getRequired().equals(TBoolean.YES)) {
271 sb.append("(required)");
273 sb.append("(optional)");
275 sb.append("\n\t\t// TODO: Set " + outputParam.getName() + " parameter here.");
276 sb.append("\n\t\t// Do NOT delete the next line of code. Set \"\" as value if you want to return nothing or an empty result!");
277 sb.append("\n\t\treturnParameters.put(\"" + outputParam.getName() + "\", \"TODO\");");
279 sb.append("\n\n\t\tsendResponse(returnParameters);\n");
282 sb.append("\t}\n\n");
285 // Read file and replace placeholders
286 Charset cs = Charset.defaultCharset();
287 List<String> lines = new ArrayList<>();
288 for (String line : Files.readAllLines(javaService.toPath(), cs)) {
289 // Replace web service method
290 line = line.replaceAll(Generator.PLACEHOLDER_GENERATED_WEBSERVICE_METHODS, sb.toString());
295 OpenOption[] options = new OpenOption[] {StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING};
296 Files.write(javaService.toPath(), lines, cs, options);
300 * Iterates recursively through all the files in the project working
301 * directory and tries to replace the global placeholders.
303 * @param folderOrFile to start with
305 private void updateFilesRecursively(File folderOrFile) {
306 if (folderOrFile.isFile()) {
308 if (folderOrFile.getAbsolutePath().endsWith(".jar")) {
312 Generator.logger.trace("Updating file " + folderOrFile);
315 // Read file and replace placeholders
316 Charset cs = Charset.defaultCharset();
317 List<String> lines = new ArrayList<>();
318 for (String line : Files.readAllLines(folderOrFile.toPath(), cs)) {
319 line = line.replaceAll(Generator.PLACEHOLDER_CLASS_NAME, this.name);
320 line = line.replaceAll(Generator.PLACEHOLDER_JAVA_PACKAGE, this.javaPackage);
321 line = line.replaceAll(Generator.PLACEHOLDER_NAMESPACE, this.namespace);
322 line = line.replaceAll(Generator.PLACEHOLDER_IA_ARTIFACT_TEMPLATE_UPLOAD_URL, this.iaArtifactTemplateUploadUrl.toString());
327 OpenOption[] options = new OpenOption[] {StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING};
328 Files.write(folderOrFile.toPath(), lines, cs, options);
330 } catch (IOException e) {
335 Generator.logger.trace("Updating folder " + folderOrFile);
336 for (File childFile : folderOrFile.listFiles()) {
337 this.updateFilesRecursively(childFile);
343 * Packages the generated project into a ZIP file which is stored in outDir
344 * and has the name of the Project.
348 private File packageProject() {
350 File packagedProject = new File(this.outDir.getAbsoluteFile() + File.separator + this.name + ".zip");
351 FileOutputStream fileOutputStream = new FileOutputStream(packagedProject);
352 final ArchiveOutputStream zos = new ArchiveStreamFactory().createArchiveOutputStream("zip", fileOutputStream);
354 this.addFilesRecursively(this.workingDir.getAbsoluteFile(), this.workingDir.getAbsoluteFile().getAbsolutePath() + File.separator, zos);
359 return packagedProject;
361 } catch (Exception e) {
368 * Recursive Helper function for packageProject()
370 * @param folderOrFile to add into the archive
372 * @param zos ArchiveOutputStream to add the files to
374 private void addFilesRecursively(File folderOrFile, String baseDir, ArchiveOutputStream zos) {
375 if (folderOrFile.isFile()) {
376 String nameOfFileInZip = folderOrFile.getAbsolutePath().replace(baseDir, "");
377 Generator.logger.trace("Adding " + folderOrFile + " as " + nameOfFileInZip);
378 ArchiveEntry archiveEntry = new ZipArchiveEntry(nameOfFileInZip);
379 try (InputStream is = new FileInputStream(folderOrFile)) {
380 zos.putArchiveEntry(archiveEntry);
381 IOUtils.copy(is, zos);
382 zos.closeArchiveEntry();
383 } catch (Exception e) {
387 Generator.logger.trace("Adding folder " + folderOrFile);
388 for (File childFile : folderOrFile.listFiles()) {
389 this.addFilesRecursively(childFile, baseDir, zos);