Add winery source code
[vfc/nfvo/wfengine.git] / winery / org.eclipse.winery.generators.ia / src / main / java / org / eclipse / winery / generators / ia / Generator.java
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
8  *
9  * Contributors:
10  *     Tobias Binz - initial API and implementation
11  *******************************************************************************/
12 package org.eclipse.winery.generators.ia;
13
14 import java.io.File;
15 import java.io.FileInputStream;
16 import java.io.FileOutputStream;
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.net.URL;
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;
31
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;
44
45 public class Generator {
46         
47         private static final Logger logger = LoggerFactory.getLogger(Generator.class);
48         
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";
54         
55         // Placeholders in Java Service Files
56         public static final String PLACEHOLDER_GENERATED_WEBSERVICE_METHODS = "GENERATED_WEBSERVICE_METHODS";
57         
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";
61         
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";
64         
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;
72         
73         
74         /**
75          * Creates a new IA Generator instance for the given {@link TInterface}.
76          * 
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
84          *            should be posted.
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
89          *            generating it.
90          */
91         public Generator(TInterface tinterface, String packageAndNamespace, URL iaArtifactTemplateUploadUrl, String name, File workingDir) {
92                 super();
93                 this.tinterface = tinterface;
94                 this.javaPackage = packageAndNamespace;
95                 this.iaArtifactTemplateUploadUrl = iaArtifactTemplateUploadUrl;
96                 this.name = name;
97                 this.workingDir = new File(workingDir.getAbsolutePath() + File.separator + this.name);
98                 this.outDir = new File(workingDir.getAbsolutePath());
99                 
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!");
102                 }
103                 
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
110                         if (i != 0) {
111                                 tmpNamespace += ".";
112                         }
113                 }
114                 this.namespace = tmpNamespace += "/";
115         }
116         
117         /**
118          * Generates the IA project.
119          * 
120          * @return The ZIP file containing the maven/eclipse project to be
121          *         downloaded by the user.
122          */
123         public File generateProject() {
124                 
125                 try {
126                         Path workingDirPath = this.workingDir.toPath();
127                         Files.createDirectories(workingDirPath);
128                         
129                         // directory to store the template files to generate the java files from
130                         Path javaTemplateDir = workingDirPath.resolve("../java");
131                         Files.createDirectories(javaTemplateDir);
132                         
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);
138                                 
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());
145                                 
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()) {
155                                                                 // directory found
156                                                                 Path dir = workingDirPath.resolve(name);
157                                                                 Files.createDirectory(dir);
158                                                         } else {
159                                                                 Path file = workingDirPath.resolve(name);
160                                                                 try (InputStream is = jf.getInputStream(je);) {
161                                                                         Files.copy(is, file, StandardCopyOption.REPLACE_EXISTING);
162                                                                 }
163                                                         }
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);
171                                                                 }
172                                                         }
173                                                 }
174                                         }
175                                 }
176                         } else {
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);
180                                 
181                                 File javaTemplatesDir = new File(this.getClass().getResource("/" + Generator.TEMPLATE_JAVA_FOLDER).getFile());
182                                 FileUtils.copyDirectory(javaTemplatesDir, javaTemplateDir.toFile());
183                         }
184                         
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];
190                         }
191                         
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);
197                         
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);
203                         
204                         this.generateJavaFile(javaService);
205                         this.updateFilesRecursively(this.workingDir);
206                         return this.packageProject();
207                         
208                 } catch (Exception e) {
209                         e.printStackTrace();
210                         return null;
211                 }
212         }
213         
214         private void generateJavaFile(File javaService) throws IOException {
215                 
216                 // Generate methods
217                 StringBuilder sb = new StringBuilder();
218                 
219                 for (TOperation op : this.tinterface.getOperation()) {
220                         // Annotations
221                         sb.append("\t@WebMethod\n");
222                         sb.append("\t@SOAPBinding\n");
223                         sb.append("\t@Oneway\n");
224                         
225                         // Signatur
226                         String operationReturn = "void";
227                         sb.append("\tpublic " + operationReturn + " " + op.getName() + "(\n");
228                         
229                         // Parameter
230                         boolean first = true;
231                         if (op.getInputParameters() != null) {
232                                 for (TParameter parameter : op.getInputParameters().getInputParameter()) {
233                                         String parameterName = parameter.getName();
234                                         
235                                         if (first) {
236                                                 first = false;
237                                                 sb.append("\t\t");
238                                         } else {
239                                                 sb.append(",\n\t\t");
240                                         }
241                                         
242                                         // Generate @WebParam
243                                         sb.append("@WebParam(name=\"" + parameterName + "\", targetNamespace=\"" + this.namespace + "\") ");
244                                         
245                                         // Handle required and optional parameters using @XmlElement
246                                         if (parameter.getRequired().equals(TBoolean.YES)) {
247                                                 sb.append("@XmlElement(required=true)");
248                                         } else {
249                                                 sb.append("@XmlElement(required=false)");
250                                         }
251                                         
252                                         sb.append(" String " + parameterName);
253                                 }
254                         }
255                         sb.append("\n\t) {\n");
256                         
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");
262                         }
263                         
264                         sb.append("\t\t// TODO: Implement your operation here.\n");
265                         
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)");
272                                         } else {
273                                                 sb.append("(optional)");
274                                         }
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\");");
278                                 }
279                                 sb.append("\n\n\t\tsendResponse(returnParameters);\n");
280                         }
281                         
282                         sb.append("\t}\n\n");
283                 }
284                 
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());
291                         lines.add(line);
292                 }
293                 
294                 // Write file
295                 OpenOption[] options = new OpenOption[] {StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING};
296                 Files.write(javaService.toPath(), lines, cs, options);
297         }
298         
299         /**
300          * Iterates recursively through all the files in the project working
301          * directory and tries to replace the global placeholders.
302          * 
303          * @param folderOrFile to start with
304          */
305         private void updateFilesRecursively(File folderOrFile) {
306                 if (folderOrFile.isFile()) {
307                         
308                         if (folderOrFile.getAbsolutePath().endsWith(".jar")) {
309                                 return;
310                         }
311                         
312                         Generator.logger.trace("Updating file " + folderOrFile);
313                         
314                         try {
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());
323                                         lines.add(line);
324                                 }
325                                 
326                                 // Write file
327                                 OpenOption[] options = new OpenOption[] {StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING};
328                                 Files.write(folderOrFile.toPath(), lines, cs, options);
329                                 
330                         } catch (IOException e) {
331                                 e.printStackTrace();
332                         }
333                         
334                 } else {
335                         Generator.logger.trace("Updating folder " + folderOrFile);
336                         for (File childFile : folderOrFile.listFiles()) {
337                                 this.updateFilesRecursively(childFile);
338                         }
339                 }
340         }
341         
342         /**
343          * Packages the generated project into a ZIP file which is stored in outDir
344          * and has the name of the Project.
345          * 
346          * @return ZIP file
347          */
348         private File packageProject() {
349                 try {
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);
353                         
354                         this.addFilesRecursively(this.workingDir.getAbsoluteFile(), this.workingDir.getAbsoluteFile().getAbsolutePath() + File.separator, zos);
355                         
356                         zos.finish();
357                         zos.close();
358                         
359                         return packagedProject;
360                         
361                 } catch (Exception e) {
362                         e.printStackTrace();
363                 }
364                 return null;
365         }
366         
367         /**
368          * Recursive Helper function for packageProject()
369          * 
370          * @param folderOrFile to add into the archive
371          * @param baseDir
372          * @param zos ArchiveOutputStream to add the files to
373          */
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) {
384                                 e.printStackTrace();
385                         }
386                 } else {
387                         Generator.logger.trace("Adding folder " + folderOrFile);
388                         for (File childFile : folderOrFile.listFiles()) {
389                                 this.addFilesRecursively(childFile, baseDir, zos);
390                         }
391                 }
392         }
393 }