package org.eclipse.basyx.components.servlets; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.PrintWriter; | |
import java.net.URLDecoder; | |
import java.util.Collection; | |
import java.util.HashMap; | |
import java.util.HashSet; | |
import java.util.Map; | |
import java.util.Properties; | |
import java.util.Set; | |
import javax.servlet.ServletException; | |
import javax.servlet.http.HttpServletRequest; | |
import javax.servlet.http.HttpServletResponse; | |
import org.eclipse.basyx.aas.backend.modelprovider.http.BasysHTTPServelet; | |
import org.eclipse.basyx.components.directory.AASDirectoryEntry; | |
import org.eclipse.basyx.components.directory.exception.AASDirectoryProviderException; | |
/** | |
* Static configuration file based directory provider | |
* | |
* This directory provider provides a static directory. It therefore only supports get() operations. | |
* Modification of the directory via PUT/POST/PATCH/DELETE operations is not supported. | |
* | |
* @author kuhn | |
* | |
*/ | |
public class StaticCFGDirectoryServlet extends BasysHTTPServelet { | |
/** | |
* Version information to identify the version of serialized instances | |
*/ | |
private static final long serialVersionUID = 1L; | |
/** | |
* Configuration properties (raw input from file) | |
*/ | |
protected Properties properties = null; | |
/** | |
* Asset administration shells by ID | |
*/ | |
protected Map<String, AASDirectoryEntry> aasByID = new HashMap<>(); | |
/** | |
* Asset administration shells by tag | |
*/ | |
protected Map<String, Collection<AASDirectoryEntry>> aasByTag = new HashMap<>(); | |
/** | |
* Uplink server | |
*/ | |
protected String uplink = null; | |
/** | |
* Downlink servers | |
*/ | |
protected Map<String, String> downlinks = new HashMap<>(); | |
/** | |
* Constructor | |
*/ | |
public StaticCFGDirectoryServlet() { | |
// Invoke base constructor | |
super(); | |
} | |
/** | |
* Load a property | |
*/ | |
protected String extractProperty(Properties prop, String key) { | |
// Check if properties contain value | |
if (!prop.containsKey(key)) return null; | |
// Extract and remove value | |
String value = (String) prop.get(key); | |
prop.remove(key); | |
// Return value | |
return value; | |
} | |
/** | |
* Extract property keys with prefix and suffix. Prefix and suffix is removed from key. | |
*/ | |
protected Collection<String> getProperties(Properties prop, String prefix, String suffix) { | |
// Store result | |
HashSet<String> result = new HashSet<>(); | |
// Iterate keys | |
for (String key: prop.stringPropertyNames()) { | |
if (key.startsWith(prefix) && key.endsWith(suffix)) result.add(key.substring(prefix.length(), key.length()-suffix.length())); | |
} | |
// Return result | |
return result; | |
} | |
/** | |
* Extract downlink servers | |
*/ | |
protected Map<String, String> extractDownlinks(Properties prop) { | |
// Return value | |
Map<String, String> result = new HashMap<>(); | |
// Downlink server names | |
Collection<String> downlinkServerNames = getProperties(prop, "cfg.downlink.", ".pattern"); | |
// Remove downlink pattern and server URL | |
for (String name: downlinkServerNames) { | |
// Get downlink pattern and server URL | |
result.put(prop.getProperty("cfg.downlink."+name+".pattern"), prop.getProperty("cfg.downlink."+name+".directory")); | |
// Remove pattern and directory properties | |
prop.remove("cfg.downlink."+name+".pattern"); | |
prop.remove("cfg.downlink."+name+".directory"); | |
} | |
// Return downlink server mappings | |
return result; | |
} | |
/** | |
* Extract Asset Administration Shell definitions | |
*/ | |
protected Map<String, AASDirectoryEntry> extractAAS(Properties prop) { | |
// Return value | |
Map<String, AASDirectoryEntry> result = new HashMap<>(); | |
// Get AAS IDs | |
Collection<String> aasIDs = getProperties(prop, "", ".id"); | |
// Create AAS directory entries from properties | |
for (String aasID : aasIDs) { | |
// Create AAS directory entry | |
AASDirectoryEntry entry = new AASDirectoryEntry(prop.getProperty(aasID+".id"), prop.getProperty(aasID+".aas"), prop.getProperty(aasID+".type"), prop.getProperty(aasID+".tags")); | |
// Add AAS directory entry | |
result.put(prop.getProperty(aasID+".id"), entry); | |
} | |
// Return ID to AAS mappings | |
return result; | |
} | |
/** | |
* Map AAS tags to AAS | |
*/ | |
protected Map<String, Collection<AASDirectoryEntry>> mapAASToTags(Map<String, AASDirectoryEntry> aasByID) { | |
// Return value | |
Map<String, Collection<AASDirectoryEntry>> result = new HashMap<>(); | |
// Iterate AAS directory entries | |
for (AASDirectoryEntry dirEntry: aasByID.values()) { | |
// Process tags | |
for (String tag: dirEntry.getAASTags()) { | |
// Create tag if necessary | |
if (!result.containsKey(tag)) {result.put(tag, new HashSet<AASDirectoryEntry>());} | |
// Add AAS to tag | |
result.get(tag).add(dirEntry); | |
} | |
} | |
// Return HashTag to AAS mappings | |
return result; | |
} | |
/** | |
* Load properties from file | |
*/ | |
protected void loadProperties(String cfgFilePath) { | |
// Read property file | |
try { | |
// Open property file | |
InputStream input = getServletContext().getResourceAsStream(cfgFilePath); | |
// Instantiate property structure | |
properties = new Properties(); | |
properties.load(input); | |
System.out.println("properties:"+properties); | |
System.out.println("properties (keys):"+properties.keySet()); | |
System.out.println("properties (cfg.downlink.is.pattern):"+properties.get("cfg.downlink.is.pattern")); | |
// Process properties | |
// - Uplink server | |
uplink = extractProperty(properties, "cfg.uplink"); | |
// - Downlink servers | |
downlinks = extractDownlinks(properties); | |
// - AAS by ID | |
aasByID = extractAAS(properties); | |
// - AAS by tag | |
aasByTag = mapAASToTags(aasByID); | |
System.out.println("Downlink:"+downlinks); | |
System.out.println("properties:"+properties); | |
System.out.println("aasbyID:"+aasByID); | |
} catch (IOException e) { | |
// Output exception | |
e.printStackTrace(); | |
} | |
} | |
/** | |
* Initialize servlet | |
* | |
* @throws ServletException | |
*/ | |
public void init() throws ServletException { | |
// Call base implementation | |
super.init(); | |
// Read configuration values | |
String configFilePath = (String) getInitParameter("config"); | |
// - Read property file | |
loadProperties(configFilePath); | |
} | |
/** | |
* Send a response | |
*/ | |
protected void sendResponse(String value, PrintWriter outputStream) { | |
// Null pointer check | |
if (value == null) return; | |
// Output result | |
outputStream.write(value); | |
outputStream.flush(); | |
} | |
/** | |
* Get AAS content from AASDirectoryEntry | |
*/ | |
protected String getAASContent(AASDirectoryEntry directoryEntry) { | |
// Process directory entry | |
switch (directoryEntry.getAASContentType()) { | |
// Local content type | |
case AASDirectoryEntry.AAS_CONTENTTYPE_LOCAL: | |
return directoryEntry.getAASContent(); | |
// Remote content type | |
case AASDirectoryEntry.AAS_CONTENTTYPE_REMOTE: | |
throw new AASDirectoryProviderException("Unsupported AAS content type"); | |
// Proxy content type - content is ID of AAS that contains the information | |
case AASDirectoryEntry.AAS_CONTENTTYPE_PROXY: | |
return getAASContentByID(directoryEntry.getAASContent()); | |
// Unknown content type | |
default: | |
throw new AASDirectoryProviderException("Unknown AAS content type"); | |
} | |
} | |
/** | |
* Get requested tags as collection | |
*/ | |
protected Collection<String> getTagsAsCollection(String tags) { | |
// Collection stores AAS tags | |
Collection<String> alltags = new HashSet<>(); | |
// Catch null pointer exceptions | |
try { | |
// Get AAS tags | |
String[] splitTags = tags.split(","); | |
// Only add non-empty tags | |
for (String tag: splitTags) if (tag.trim().length() > 0) alltags.add(tag.trim()); | |
} catch (NullPointerException e) {} | |
// Return all tags | |
return alltags; | |
} | |
/** | |
* Get a specific AAS content with ID | |
*/ | |
protected String getAASContentByID(String aasID) { | |
// Extract requested AAS ID | |
AASDirectoryEntry aas = aasByID.get(aasID); | |
// Null pointer check | |
if (aas == null) return null; | |
// Return result | |
return getAASContent(aas); | |
} | |
/** | |
* Implement "Get" operation | |
* | |
* Process HTTP get request - get sub model property value | |
*/ | |
@Override | |
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { | |
// Process request depending on the path and on parameter | |
String uri = req.getRequestURI(); | |
String contextPath = req.getContextPath(); | |
String path = URLDecoder.decode(uri.substring(contextPath.length()+1).substring(req.getServletPath().length()), "UTF-8"); // plus 1 for "/" | |
// Extract action parameter | |
Collection<String> alltags = getTagsAsCollection(req.getParameter("tags")); | |
// Setup HTML response header | |
resp.setContentType("application/json"); | |
resp.setCharacterEncoding("UTF-8"); | |
// Process get request | |
// - Get all (local) AAS | |
if (path.equals("api/v1/registry")) { | |
// Extract AAS directory entries | |
Collection<AASDirectoryEntry> entries = null; | |
// Check if tags are to be processed | |
if (alltags.isEmpty()) { | |
// Get all tags | |
entries = aasByID.values(); | |
} else { | |
// HashSet that holds all elements | |
Set<AASDirectoryEntry> taggedEntries = new HashSet<>(); | |
// Get tagged elements that have all requested tags | |
// - Get first requested tag | |
taggedEntries.addAll(aasByTag.get(alltags.iterator().next())); | |
// - Remove all directory entries that do not have all tags | |
for (String tag: alltags) taggedEntries.retainAll(aasByTag.get(tag)); | |
// - Place remaining elements into entries collection | |
entries = taggedEntries; | |
} | |
// Build response string | |
StringBuilder response = new StringBuilder(); | |
for (AASDirectoryEntry entry: entries) response.append(getAASContent(entry)); | |
// Write result | |
sendResponse(response.toString(), resp.getWriter()); | |
// End processing | |
return; | |
} | |
// Get a specific AAS | |
if (path.startsWith("api/v1/registry/")) { | |
System.out.println("Getting:"+path); | |
// Get requested AAS with ID | |
String aas = getAASContentByID(path.substring(new String("api/v1/registry/").length())); | |
// Write result | |
sendResponse(aas, resp.getWriter()); | |
// End processing | |
return; | |
} | |
} | |
/** | |
* Implement "Put" operation | |
*/ | |
@Override | |
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { | |
// Indicate an unsupported operation | |
throw new AASDirectoryProviderException("Unsupported operation"); | |
} | |
/** | |
* <pre> | |
* Handle HTTP POST operation. Creates a new Property, Operation, Event, Submodel or AAS or invokes an operation. | |
*/ | |
@Override | |
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { | |
// Indicate an unsupported operation | |
throw new AASDirectoryProviderException("Unsupported operation"); | |
} | |
/** | |
* Handle a HTTP PATCH operation. Updates a map or collection respective to action string. | |
* | |
*/ | |
@Override | |
protected void doPatch(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { | |
// Indicate an unsupported operation | |
throw new AASDirectoryProviderException("Unsupported operation"); | |
} | |
/** | |
* Implement "Delete" operation. Deletes any resource under the given path. | |
*/ | |
@Override | |
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { | |
// Indicate an unsupported operation | |
throw new AASDirectoryProviderException("Unsupported operation"); | |
} | |
} | |