blob: 6657bbabab1dfc879e41384c17c52064f5cbf55c [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2021 the Eclipse BaSyx Authors
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
******************************************************************************/
package org.eclipse.basyx.vab.modelprovider;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.basyx.submodel.metamodel.map.submodelelement.operation.Operation;
import org.eclipse.basyx.vab.exception.provider.MalformedRequestException;
/**
* Utility functions to handle a VAB path
*
* @author kuhn, espen
*
*/
public class VABPathTools {
public static final String SEPERATOR = "/";
/**
* Removes leading and trailing slashes
*
* @param path
* @return
*/
public static String stripSlashes(String path) {
while (path.startsWith("/")) {
path = path.substring(1);
}
while (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
return path;
}
/**
* Encodes sensitive characters, e.g. "/" and "#"
*
* @param elem
* @return
*/
public static String encodePathElement(String elem) {
try {
return URLEncoder.encode(elem, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
/**
* Decodes sensitive characters, e.g. "/" and "#"
*
* @param elem
* @return
*/
public static String decodePathElement(String encodedElem) {
try {
return URLDecoder.decode(encodedElem, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
/**
* Skips the first N entries of a path. E.g. for <i>a/b/c</i> skipping 2 means
* returning <i>c</i>
*
* @param path
* @param toSkip
* @return
*/
public static String skipEntries(String path, int toSkip) {
StringBuilder builder = new StringBuilder();
String[] splitted = VABPathTools.splitPath(path);
for (int i = toSkip; i < splitted.length; i++) {
builder.append(splitted[i]);
// Don't add slash at last step
if (i < splitted.length - 1) {
builder.append("/");
}
}
return builder.toString();
}
/**
* Returns the Nth entry of a path, e.g. the second entry of <i>a/b/c</i> is
* <i>c</i>
*
* @param path
* @param entry
* @return
*/
public static String getEntry(String path, int entry) {
return VABPathTools.splitPath(path)[entry];
}
/**
* Split a path into path elements, e.g. /a/b/c -> [ a, b, c ]
*/
public static String[] splitPath(String path) {
// Return null result for null argument
if (path == null) {
return null;
}
// includes null-values, "" and "/";
if (VABPathTools.isEmptyPath(path)) {
return new String[] {};
}
// Remove leading separator, otherwise /a leads to {"", "a"}
String fixedPath = removePrefix(path, SEPERATOR);
String[] splitted = fixedPath.split(SEPERATOR);
List<String> nonEmptySplitted = new ArrayList<>();
// Remove empty entries
for (String s : splitted) {
if (!s.isEmpty()) {
nonEmptySplitted.add(s);
}
}
return nonEmptySplitted.toArray(new String[nonEmptySplitted.size()]);
}
/**
* Remove the last element from the path
*/
public static String getParentPath(String path) {
// Return null result for null argument
if (path == null) {
return null;
}
if (isEmptyPath(path)) {
return "";
}
int lastIndex = path.lastIndexOf(SEPERATOR);
if (lastIndex == path.length() - 1) {
lastIndex = path.lastIndexOf(SEPERATOR, path.length() - 2);
}
if (lastIndex >= 0) {
return removePrefix(path.substring(0, lastIndex), SEPERATOR);
} else {
return "";
}
}
/**
* Get the last element of a path. Return "" if there is no element in the path
*/
public static String getLastElement(String path) {
// Return null result for null argument
if (path == null) {
return null;
}
String[] elements = splitPath(path);
if (elements.length > 0) {
return elements[elements.length - 1];
} else {
return "";
}
}
/**
* Remove prefix from beginning of path
*/
public static String removePrefix(String path, String prefix) {
// Return null result for null argument
if (path == null) {
return null;
}
if (VABPathTools.isEmptyPath(path)) {
// same result as for any other "empty" path, like "" and "/"
return "";
}
if (path.startsWith(prefix)) {
return path.substring(prefix.length());
} else {
return path;
}
}
public static String append(String path, String element) {
// Return null result for null argument
if (path == null || element == null) {
return null;
}
if (path.lastIndexOf(SEPERATOR) == path.length() - 1) {
return path + element;
} else {
return path + SEPERATOR + element;
}
}
/**
* Build and return a path with pathElements[startIndex] as the root element
*/
public static String buildPath(String[] pathElements, int startIndex) {
// Return null result for null argument
if (pathElements == null) {
return null;
}
if (startIndex >= pathElements.length) {
return "";
}
// This will store the resulting path
StringBuilder result = new StringBuilder();
// Build path
for (int i = startIndex; i < pathElements.length; i++)
result.append(pathElements[i] + SEPERATOR);
// Remove last '/'
result.deleteCharAt(result.length() - 1);
// Return created path
return result.toString();
}
/**
* Check if the path to an VAB elements leads to the invocation of an operation. In this case, the
* element path conforms to /aas/submodels/{subModelId}/submodelElements/{operationId}/invoke
*/
public static boolean isOperationInvokationPath(String path) {
// null-Paths are no operation paths
if (path == null) {
return false;
}
// Split path
String[] pathElements = splitPath(path);
if(pathElements.length == 0) {
return false;
}
// Check if last path element is "invoke" or "operations" is contained anywhere
return pathElements[pathElements.length - 1].startsWith(Operation.INVOKE) || isOperationPath(path);
}
private static boolean isOperationPath(String path) {
String lowerCasePath = path.toLowerCase();
return lowerCasePath.startsWith("operations/") || path.toLowerCase().contains("/operations/");
}
/**
* Check, if the path does not contain any elements.
*/
public static boolean isEmptyPath(String path) {
return path.equals("") || path.equals("/");
}
/**
* Gets the first endpoint of a path.
* @path
* A path that can contain 0..* endpoints.
* @return
* The first address entry of a path. The address entry is the first endpoint combined with a protocol.
* If there is no protocol defined, the address entry is empty ("").
* E.g. basyx://127.0.0.1:6998//https://localhost/test/ will return basyx://127.0.0.1:6998,
* https://localhost/test//basyx://127.0.0.1:6998/ will return https://localhost/test
* and http://localhost/test/ will return "".
*/
public static String getFirstEndpoint(String fullPath) {
// Return null result for null argument
if (fullPath == null) {
return null;
}
if (isEmptyPath(fullPath) || !fullPath.contains("//")) {
return "";
} else {
String[] splitted = fullPath.split("//");
return splitted[0] + "//" + splitted[1];
}
}
/**
* Removes the first endpoint from a path. See {@link #getAddressEntry}<br/>
* @path
* @return
* The first endpoint. E.g. basyx://127.0.0.1:6998//https://localhost/test/ will return
* https://localhost/test/.
*
*/
public static String removeFirstEndpoint(String fullPath) {
// Return null result for null argument
if (fullPath == null) {
return null;
}
if (isEmptyPath(fullPath)) {
return "";
} else if (!fullPath.contains("//")) {
return fullPath;
} else {
String firstEndpoint = fullPath.replaceFirst(getFirstEndpoint(fullPath), "");
if (firstEndpoint.startsWith("//")) {
firstEndpoint = firstEndpoint.replaceFirst("//", "");
}
return firstEndpoint;
}
}
/**
* Concatenate two paths
*/
public static String concatenatePaths(String... paths) {
// Return null result for null argument
if (paths == null || paths.length == 0) {
return null;
}
// Store result
StringBuffer result = new StringBuffer();
// Flag that indicates whether processed path segment is first segment
boolean isFirst = true;
// Process all path segments
for (String pathSegment : paths) {
// Return empty result, if any element is null
if (pathSegment == null) {
return null;
}
// Remove leading and trailing "/" from pathsegment
while (pathSegment.endsWith("/"))
pathSegment = pathSegment.substring(0, pathSegment.length() - 1);
while (pathSegment.startsWith("/"))
pathSegment = pathSegment.substring(1);
// Add path to result; if its first segment, do not split with "'"
if (!isFirst)
result.append("/");
else
isFirst = false;
result.append(pathSegment);
}
// Return combined path
return result.toString();
}
/**
* Checks if path is null, if yes throw exception
*
* @param path
*/
public static void checkPathForNull(String path) throws MalformedRequestException {
if (path == null) {
throw new MalformedRequestException("Path is not allowed to be null");
}
}
/**
* Strips the last path element if it is "invoke"
*
* @param path
* @return path without last element "invoke" or unchanged path
*/
public static String stripInvokeFromPath(String path) {
if(path == null)
return null;
if(getLastElement(path).startsWith(Operation.INVOKE)) {
return getParentPath(path);
}
return path;
}
/**
* Gets the path from a URL
* e.g "http://localhost:8080/path/to/test.file" results in "/path/to/test.file"
*
* @param url
* @return the path from the URL
*/
public static String getPathFromURL(String url) {
if(url == null) {
return null;
}
if(url.contains("://")) {
// Find the ":" and and remove the "http://" from the url
int index = url.indexOf(":") + 3;
url = url.substring(index);
// Find the first "/" from the URL (now without the "http://") and remove everything before that
index = url.indexOf("/");
url = url.substring(index);
// Recursive call to deal with more than one server parts
// (e.g. basyx://127.0.0.1:6998//https://localhost/test/)
return getPathFromURL(url);
} else {
// Make sure the path has a / at the start
if(!url.startsWith("/")) {
url = "/" + url;
}
return url;
}
}
/**
* Harmonizes a path so that it will always and with the suffix and no ending
* slash (even if the suffix contains one).
*
* @param path
* to harmonize
* @param suffix
* to check for existance and append if necessary
* @return harmonized path
*/
public static String harmonizePathWithSuffix(String path, String suffix) {
String strippedPath = stripSlashes(path);
String strippedSuffix = stripSlashes(suffix);
if (strippedPath.endsWith("/" + strippedSuffix)) {
return strippedPath;
} else {
return VABPathTools.concatenatePaths(strippedPath, strippedSuffix);
}
}
}