| /******************************************************************************* |
| * 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); |
| |
| } |
| } |