blob: 7162daa479dcf25fd89ee1dca73c7da3fb9cdd25 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2020 DFKI.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* DFKI - Volkan Gezer <volkan.gezer@dfki.de>
*
*******************************************************************************/
package org.eclipse.aas.basyx.codegen.util;
import java.util.List;
import java.util.Set;
import org.eclipse.aas.basyx.codegen.util.submodel.SubModel;
import org.eclipse.aas.basyx.codegen.util.submodel.SubModelCreator;
public class Project {
private DataConnector sdc;
private SubModelCreator mc;
private String deviceendpoint;
private String externalClassFolder = FileUtils.getCurrentWorkingDirectory() + "/extClasses";
private String aasIP;
private String aasPort;
private String namespace = "de.dfki.canvaas.default";
private String projectName = "CanvAASProject";
private String projectFolder = projectName + "/";
public Project(String modelUrn, String aasIp, String aasPort, SubModelCreator mc) {
// Set access point to AAS
this(modelUrn, aasIp, aasPort, mc, "");
}
public String getProjectName() {
return projectName;
}
/**
* Defines where the external classes are stored. They are automatically copied into
* project folder after project creation. If not set, uses the API's {@code extClasses} folder.
* @param externalClassFolder full path to the external class folder
*/
public void setExternalClassFolder(String externalClassFolder) {
this.externalClassFolder = externalClassFolder;
}
/**
* Sets the project folder to export the files to. If not set, creates a folder with {@code projectName}
* in API's folder.
* @param projectFolder to export the files to.
*/
public void setProjectFolder(String projectFolder) {
this.projectFolder = projectFolder;
}
public SubModelCreator getSMC() {
return mc;
}
public String getAasIP() {
return aasIP;
}
public String getAasPort() {
return aasPort;
}
public String getNamespace() {
return namespace;
}
/**
* Sets the project name. This is also used as asset name.
* @param projectName to set
*/
public void setProjectName(String projectName) {
this.projectName = projectName;
}
/**
* Sets the namespace of the classes using the project name automatically.
* If not set, defaults to {@code de.dfki.canvaas.default}.
*/
public void setNamespaceFromProjectName() {
namespace = projectName.toLowerCase().replace(" ", ".");
}
/**
* Returns the project name with lowercase and trimmed from spaces.
* @return
*/
public String getSafeProjectName() {
return projectName.toLowerCase().replace(" ", "");
}
/**
* Returns the project name without spaces.
* @return
*/
public String getProjectNameWithoutSpace() {
return projectName.replace(" ", "");
}
/**
* Sets the namespace of the project to be created. See
* {@link #setNamespaceFromProjectName()} to create it automatically.
* @param namespace to set.
*/
public void setNamespace(String namespace) {
this.namespace = namespace;
}
/**
*
* Constructor. Creates a project to write actual files.
*
* @param modelUrn a unique identifier for the model.
* @param aasIp IP address to access to the AAS server
* @param aasPort Port number to access to the server
* @param subModelCreator that collects the sub models.
* @param deviceEndPoint IP:PORT of the device in case AAS is not stored
* at the same physical location.
*/
public Project(String modelUrn, String aasIp, String aasPort, SubModelCreator subModelCreator, String deviceEndPoint) {
// Set access point to AAS
DataConnector dc = new DataConnector(modelUrn,
aasIp, aasPort, subModelCreator, this);
this.aasIP = aasIp;
this.aasPort = aasPort;
this.mc = subModelCreator;
this.sdc = dc;
this.deviceendpoint = deviceEndPoint;
}
private void createFilePaths() {
FileUtils.createDirectories(projectFolder + "src/" + FileUtils.getFullPathFromNamespace(namespace) + "/connection");
// FileUtils.createDirectories(fullPath + "/module"); // Optional because of next line
FileUtils.createDirectories(projectFolder + "src/" + FileUtils.getFullPathFromNamespace(namespace) + "/module/submodel");
FileUtils.createDirectories(projectFolder + "/includes");
//FileUtils.createDirectories(projectFolder + "/includes/basyx");
//FileUtils.createDirectories(projectFolder + "/includes/basyx/basyx.sdk/");
FileUtils.createDirectories(projectFolder + "/includes/basyx/basyx.sdk/0.1.0-SNAPSHOT/");
//FileUtils.createDirectories(projectFolder + "/includes/org.json/");
//FileUtils.createDirectories(projectFolder + "/includes/org.json/json/");
FileUtils.createDirectories(projectFolder + "/includes/org.json/json/20200518/");
}
private void createConnectionFiles() {
createFilePaths();
// ConnectedDevice
FileUtils.writeData(projectFolder + "src/" + FileUtils.getFullPathFromNamespace(namespace) + "/connection/ConnectedDevice.java", sdc.createConnectedDevice());
// DataCrawler
FileUtils.writeData(projectFolder + "src/" + FileUtils.getFullPathFromNamespace(namespace) + "/connection/DataCrawler.java", sdc.createDataCrawler());
// OPCUAConnectorWrapper
FileUtils.writeData(projectFolder + "src/" + FileUtils.getFullPathFromNamespace(namespace) + "/connection/OPCUAConnectorWrapper.java", sdc.createOPCUAConnectorWrapper());
}
private void createModuleFiles() {
createFilePaths();
// DeviceAASServer
FileUtils.writeData(projectFolder + "src/" + FileUtils.getFullPathFromNamespace(namespace) + "/module/DeviceAASServer.java", sdc.createDeviceAASServer(deviceendpoint));
// DeviceContext
FileUtils.writeData(projectFolder + "src/" + FileUtils.getFullPathFromNamespace(namespace) + "/module/DeviceContext.java", sdc.createDeviceContext());
}
private void createSubModelFiles() {
createFilePaths();
// ModelFile
List<SubModel> models = mc.getSubModels();
for (SubModel submodel : models) {
System.out.println("Creating submodel: " + submodel.getName());
FileUtils.writeData(projectFolder + "src/" + FileUtils.getFullPathFromNamespace(namespace) + "/module/submodel/" +
submodel.getName().replace(" ", "") + ".java", mc.createSubModelFile(submodel.getName(), this));
}
// DeviceAAS
FileUtils.writeData(projectFolder + "src/" + FileUtils.getFullPathFromNamespace(namespace) + "/module/submodel/DeviceAAS.java", sdc.createDeviceAAS());
}
private void createProjectFile() {
String text = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" +
"<projectDescription>\r\n" +
" <name>" + getProjectName() + "</name>\r\n" +
" <comment></comment>\r\n" +
" <projects>\r\n" +
" </projects>\r\n" +
" <buildSpec>\r\n" +
" <buildCommand>\r\n" +
" <name>org.eclipse.jdt.core.javabuilder</name>\r\n" +
" <arguments>\r\n" +
" </arguments>\r\n" +
" </buildCommand>\r\n" +
" <buildCommand>\r\n" +
" <name>org.eclipse.m2e.core.maven2Builder</name>\r\n" +
" <arguments>\r\n" +
" </arguments>\r\n" +
" </buildCommand>\r\n" +
" </buildSpec>\r\n" +
" <natures>\r\n" +
" <nature>org.eclipse.jdt.core.javanature</nature>\r\n" +
" </natures>\r\n" +
"</projectDescription>\r\n";
FileUtils.writeData(projectFolder + ".project", text);
}
private void createPOMFile() {
String text = "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\r\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n" +
" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\r\n" +
" <modelVersion>4.0.0</modelVersion>\r\n" +
"\r\n" +
" <groupId>de.dfki.canvaas</groupId>\r\n" +
" <artifactId>canvaas." + getSafeProjectName() + "</artifactId>\r\n" +
" <version>0.0.1-SNAPSHOT</version>\r\n" +
" <name>" + getProjectName() + "</name>\r\n" +
"\r\n" +
" <packaging>jar</packaging>\r\n" +
"\r\n" +
" <properties>\r\n" +
" <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\r\n" +
" <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\r\n" +
" </properties>\r\n" +
"\r\n" +
" <!-- Specify a repository in the project dir -->\r\n" +
" <repositories>\r\n" +
" <repository>\r\n" +
" <id>data-local</id>\r\n" +
" <name>data</name>\r\n" +
" <url>file://${project.basedir}/includes</url>\r\n" +
" </repository>\r\n" +
" </repositories>\r\n" +
"\r\n" +
" <build>\r\n" +
" <!-- specify custom source directory (default maven source folder is /src/main/java) -->\r\n" +
" <sourceDirectory>/src</sourceDirectory>\r\n" +
"\r\n" +
" <plugins>\r\n" +
" <!-- Compile Sources using Java 8 -->\r\n" +
" <plugin>\r\n" +
" <artifactId>maven-compiler-plugin</artifactId>\r\n" +
" <version>3.8.1</version>\r\n" +
" <configuration>\r\n" +
" <source>1.8</source>\r\n" +
" <target>1.8</target>\r\n" +
" </configuration>\r\n" +
" </plugin>\r\n" +
" </plugins>\r\n" +
" </build>\r\n" +
"\r\n" +
" <dependencies> \r\n" +
" <!-- Add BaSys SDK from local repository. SDK has to be installed previously -->\r\n" +
" <dependency>\r\n" +
" <groupId>basyx</groupId>\r\n" +
" <artifactId>basyx.sdk</artifactId>\r\n" +
" <version>0.1.0-SNAPSHOT</version>\r\n" +
" </dependency>\r\n" +
" </dependencies>\r\n" +
"</project>";
FileUtils.writeData(projectFolder + "pom.xml", text);
}
private void createClassFile() {
String text = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" +
"<classpath>\r\n" +
" <classpathentry kind=\"src\" output=\"target/classes\" path=\"src\">\r\n" +
" <attributes>\r\n" +
" <attribute name=\"optional\" value=\"true\"/>\r\n" +
" <attribute name=\"maven.pomderived\" value=\"true\"/>\r\n" +
" </attributes>\r\n" +
" </classpathentry>\r\n" +
" <classpathentry kind=\"con\" path=\"org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8\">\r\n" +
" <attributes>\r\n" +
" <attribute name=\"maven.pomderived\" value=\"true\"/>\r\n" +
" </attributes>\r\n" +
" </classpathentry>\r\n" +
" <classpathentry kind=\"con\" path=\"org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER\">\r\n" +
" <attributes>\r\n" +
" <attribute name=\"maven.pomderived\" value=\"true\"/>\r\n" +
" </attributes>\r\n" +
" </classpathentry>\r\n" +
" <classpathentry kind=\"con\" path=\"org.eclipse.jdt.junit.JUNIT_CONTAINER/5\"/>\r\n" +
" <classpathentry kind=\"lib\" path=\"includes/basyx/basyx.components.lib-0.1.0-SNAPSHOT.jar\"/>\r\n" +
" <classpathentry kind=\"lib\" path=\"includes/basyx/basyx.examples-0.1.0-SNAPSHOT.jar\"/>\r\n" +
" <classpathentry kind=\"lib\" path=\"includes/org.json/json/20200518/json-20200518.jar\"/>\r\n" +
" <classpathentry kind=\"output\" path=\"target/classes\"/>\r\n" +
"</classpath>\r\n" +
"";
FileUtils.writeData(projectFolder + ".classpath", text);
}
private void createReadMe() {
String text = "# Welcome to " + projectName + "!\r\n"
+ "\r\n"
+ "This project contains all required files to run your AAS server.\r\n"
+ "\r\n"
+ "# Running\r\n"
+ "To run the AAS without modifications, please right-click on **DeviceAASServer.java** in *"
+ getSafeProjectName() + ".module* package and choose **Run As -> Java Application**. "
+ "Then, you will see when the server is ready to be accessed on: "
+ "**http://" + getAasIP() + ":" + getAasPort() + "/" + getSafeProjectName() + "/aas/**\r\n"
+ "\r\n"
+ "The AAS Server provides a REST interface. You can see the list of commands "
+ "[here](https://app.swaggerhub.com/apis/BaSyx/basyx_submodel_http_rest_api/v1).\r\n"
+ "Using the interface, you can invoke operations and/or get property values."
+ "\r\n"
+ "# Files\r\n"
+ "\r\n"
+ "## Sub Models\r\n"
+ "\r\n"
+ "The project contains the following sub models:\r\n"
+ "\r\n";
List<SubModel> submodels = sdc.getSubModelCreator().getSubModels();
for (SubModel subModel : submodels) {
text += " - " + subModel.getName() + " (URL to access: *[http://" + getAasIP() + ":" + getAasPort() + "/" +
getSafeProjectName() + "/aas/submodels/" + subModel.getName() + "]"
+ "(http://" + getAasIP() + ":" + getAasPort() + "/" +
getSafeProjectName() + "/aas/submodels/" + subModel.getName() + ")*)\r\n";
}
text += "\r\n"
+ "You can find them inside *" + getSafeProjectName() + ".module.submodel* package.\r\n"
+ "You can edit these files to tweak properties and operations. You may need to refer to "
+ "[Basyx Documentation](https://wiki.eclipse.org/BaSyx) to work with those files manually.\r\n"
+ "\r\n"
+ "## Link\r\n"
+ "\r\n"
+ "If you linked two AAS projects, you can see pre-created methods to access remote API calls inside "
+ "*" + getSafeProjectName() + ".module.submodel.link* package. You can use these methods using static calls.\r\n";
FileUtils.writeData(projectFolder + "README.md", text );
}
private void copyLibs() {
System.out.println("Copying external classes to the project folder...");
FileUtils.copyFile(externalClassFolder + "/basyx.components.lib-0.1.0-SNAPSHOT.jar", projectFolder + "includes/basyx/basyx.components.lib-0.1.0-SNAPSHOT.jar");
FileUtils.copyFile(externalClassFolder + "/basyx.examples-0.1.0-SNAPSHOT.jar", projectFolder + "includes/basyx/basyx.examples-0.1.0-SNAPSHOT.jar");
FileUtils.copyFile(externalClassFolder + "/basyx.sdk-0.1.0-SNAPSHOT.jar", projectFolder + "includes/basyx/basyx.sdk/0.1.0-SNAPSHOT/basyx.sdk-0.1.0-SNAPSHOT.jar");
FileUtils.copyFile(externalClassFolder + "/basyx.sdk-0.1.0-SNAPSHOT.pom", projectFolder + "includes/basyx/basyx.sdk/0.1.0-SNAPSHOT/basyx.sdk-0.1.0-SNAPSHOT.pom");
FileUtils.copyFile(externalClassFolder + "/json-20200518.jar", projectFolder + "includes/org.json/json/20200518/json-20200518.jar");
}
/**
* Creates the project based on the given parameters and writes to actual files.
*/
public void createProject() {
if(getNamespace().contentEquals("de.dfki.canvaas.default")) {
System.err.println("Project namespace is not set!");
return;
}
if(getProjectName().contentEquals("CanvAASProject")) {
System.out.println("Project name is using default name: " + projectName);
}
if(mc.getSubModels().size() < 1) {
System.err.println("You need at least one model to create model files!");
return;
}
createConnectionFiles();
createModuleFiles();
createSubModelFiles();
createProjectFile();
createClassFile();
createPOMFile();
createReadMe();
copyLibs();
System.out.println("Done. Files are at: " + projectFolder);
}
/**
* Links the current AAS with another remote AAS for remote operation calls.
* Linked AAS can be called with {@code <AASProjectName><SubModelName>.OperationName(args...)}.
* E.g. TestAASSubmodel.Operation1(HashMap args). {@code args} must be variable value pair of
* the remote operation.
* @param po {@link #Project(String, String, String, SubModelCreator) Project} instance that
* collects all information of another AAS project
*/
public void linkAAS(Project po) {
FileUtils.createDirectories(projectFolder + "src/" + FileUtils.getFullPathFromNamespace(namespace) + "/module/submodel/link");
List<SubModel> submodels = po.getSMC().getSubModels();
String lowLevelOps = "";
for (SubModel subModel : submodels) { // get all submodels
Set<String> operations = subModel.getMoc().getOperations().keySet();
String operationCollection = "";
for (String operation : operations) {
operationCollection += createRemoteOperationCalls(po.getProjectName(), subModel.getName(), operation);
}
// same file multiple submodels
lowLevelOps += createRemoteCallLowLevel(po.getProjectName(), subModel.getName());
createRemoteCreateSubModelFile(po.getProjectName(), subModel.getName(), operationCollection);
}
// write to file
createRemoteCallFile(lowLevelOps);
}
private void createRemoteCreateSubModelFile(String projectName, String subModelName, String operationCollection) {
String text = "package " + getNamespace() + ".module.submodel.link;\r\n" +
"\r\n" +
"import java.util.HashMap;\r\n" +
"\r\n" +
"/**\r\n" +
" * Performs remote calls to " + projectName + " AAS's " + subModelName + " operations\r\n" +
" * @author DFKI\r\n" +
" *\r\n" +
" */\r\n" +
"public class " + projectName.substring(0, 1).toUpperCase() + projectName.substring(1) + subModelName + " {\r\n" +
"\r\n" +
" \r\n";
text += operationCollection;
text += " \r\n" +
" \r\n" +
"}\r\n";
FileUtils.writeData(projectFolder + "src/" + FileUtils.getFullPathFromNamespace(namespace) + "/module/submodel/link/" + projectName.substring(0, 1).toUpperCase() + projectName.substring(1) + subModelName +".java", text);
}
private String createRemoteOperationCalls(String projectName, String subModelName, String operation) {
String text = " /**\r\n" +
" * Calls the remote {@code " + operation + "} operation of " + projectName + " AAS's " + subModelName + " submodel \r\n" +
" * @param arguments Use a {@code HashMap<String, String>} to send argument/value pair\r\n" +
" * \r\n" +
" * @throws Exception\r\n" +
" */\r\n" +
" public static void " + operation + "(HashMap<String, String> arguments) throws Exception {\r\n" +
" \r\n" +
" RemoteOperationCaller.call"+ projectName + subModelName + "Operation(\"" + operation + "\", arguments);\r\n" +
" }\r\n";
return text;
}
private String createRemoteCallLowLevel(String projectName, String subModelName) {
String text = " public static void call" + projectName + subModelName + "Operation(String operation, HashMap<String, String> arguments) {\r\n" +
" try {\r\n" +
" invokeOperation(\"http://" + aasIP + ":" + aasPort + "/test/aas\", \"" + subModelName + "\", operation, arguments);\r\n" +
" } catch (Exception e) {\r\n" +
" // TODO Auto-generated catch block\r\n" +
" e.printStackTrace();\r\n" +
" }\r\n" +
" }\r\n";
return text;
}
private void createRemoteCallFile(String lowLevelOps) {
String text = "package " + getNamespace() + ".module.submodel.link;\r\n" +
"\r\n" +
"import java.io.OutputStreamWriter;\r\n" +
"import java.net.HttpURLConnection;\r\n" +
"import java.net.URL;\r\n" +
"import java.util.HashMap;\r\n" +
"import java.util.Scanner;\r\n" +
"\r\n" +
"import org.json.JSONObject;\r\n" +
"\r\n" +
"public class RemoteOperationCaller {\r\n" +
"\r\n" +
" private static void invokeOperation(String aasEndPoint, String subModel, String operation,\r\n" +
" HashMap<String, String> arguments) throws Exception {\r\n" +
"\r\n" +
" HttpURLConnection connection = (HttpURLConnection) new URL(\r\n" +
" aasEndPoint + \"/submodels/\" + subModel + \"/operations/\" + operation + \"/\").openConnection();\r\n" +
"\r\n" +
" connection.setRequestMethod(\"POST\");\r\n" +
"\r\n" +
" connection.setDoOutput(true);\r\n" +
" OutputStreamWriter wr = new OutputStreamWriter(connection.getOutputStream());\r\n" +
"\r\n" +
" JSONObject post = new JSONObject();\r\n" +
"\r\n" +
" for (String key : arguments.keySet()) {\r\n" +
" post.put(key, arguments.get(key));\r\n" +
" }\r\n" +
"\r\n" +
" wr.write(post.toString());\r\n" +
" wr.flush();\r\n" +
"\r\n" +
" String response = \"\";\r\n" +
" Scanner scanner = new Scanner(connection.getInputStream());\r\n" +
" while (scanner.hasNextLine()) {\r\n" +
" response += scanner.nextLine();\r\n" +
" response += \"\\n\";\r\n" +
" }\r\n" +
" scanner.close();\r\n" +
"\r\n" +
" System.out.println(response);\r\n" +
" }\r\n" +
"\r\n";
text += lowLevelOps;
text += "}";
FileUtils.writeData(projectFolder + "src/" + FileUtils.getFullPathFromNamespace(namespace) + "/module/submodel/link/RemoteOperationCaller.java", text);
}
}