Skip to content

Commit

Permalink
Added options for adding extra meta data to the landing page using th…
Browse files Browse the repository at this point in the history
…e modelLoader plugin
  • Loading branch information
hylkevds committed Mar 15, 2024
1 parent ebadfe6 commit 789b23f
Show file tree
Hide file tree
Showing 9 changed files with 186 additions and 18 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

**New Features**
* Implemented OData any() filters.
* Added options for adding extra meta data to the landing page using the modelLoader plugin.

**Internal changes & Bugfixes**
* Improved generated queries when fetching entities over a one-to-many relation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@
import static de.fraunhofer.iosb.ilt.statests.util.EntityUtils.filterForException;
import static de.fraunhofer.iosb.ilt.statests.util.EntityUtils.testFilterResults;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import de.fraunhofer.iosb.ilt.frostclient.SensorThingsService;
import de.fraunhofer.iosb.ilt.frostclient.dao.Dao;
import de.fraunhofer.iosb.ilt.frostclient.exception.ServiceFailureException;
Expand All @@ -55,12 +58,15 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
import org.apache.http.ParseException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.geojson.Point;
Expand Down Expand Up @@ -93,6 +99,10 @@ private static String modelUrl(String name) {
return resourceUrl("finegrainedsecurity/model/", name);
}

private static String metaUrl(String name) {
return resourceUrl("finegrainedsecurity/", name);
}

private static String resourceUrl(String path, String name) {
try {
return IOUtils.resourceToURL(path + "/" + name, FineGrainedAuthTests.class.getClassLoader()).getFile();
Expand Down Expand Up @@ -123,6 +133,9 @@ private static String resourceUrl(String path, String name) {
SERVER_PROPERTIES.put("plugins.modelLoader.liquibaseFiles", "tablesSecurityUPR.xml");
SERVER_PROPERTIES.put("plugins.modelLoader.securityPath", "");
SERVER_PROPERTIES.put("plugins.modelLoader.securityFiles", modelUrl("secUsers.json") + ", " + modelUrl("secDatastreams.json") + ", " + modelUrl("secObservations.json") + ", " + modelUrl("secProjects.json") + ", " + modelUrl("secThings.json"));
SERVER_PROPERTIES.put("plugins.modelLoader.metadataData", "{\"conformance\": [\"testModel\"],\"testModel\": 4}");
SERVER_PROPERTIES.put("plugins.modelLoader.metadataPath", "");
SERVER_PROPERTIES.put("plugins.modelLoader.metadataFiles", metaUrl("metadata_1.json") + ", " + metaUrl("metadata_2.json"));
SERVER_PROPERTIES.put("plugins.modelLoader.idType.Role", "STRING");
SERVER_PROPERTIES.put("plugins.modelLoader.idType.User", "STRING");
SERVER_PROPERTIES.put("persistence.idGenerationMode.Role", "ClientGeneratedOnly");
Expand Down Expand Up @@ -621,6 +634,35 @@ void test_10a_ThingDelete() {
deleteForOk(ADMIN_P1, serviceAdminProject1, creator, serviceAdmin.dao(mdlSensing.etThing), THINGS);
}

@Test
void test_11_TestLandingPage() {
LOGGER.info(" test_11_TestLandingPage");
try {
JsonNode data = getRootUrl();
JsonNode settings = data.get("serverSettings");
JsonNode conformance = settings.get("conformance");
Set<String> confItems = new HashSet<>();
for (var item : conformance) {
confItems.add(item.textValue());
}
assertTrue(confItems.contains("testModel"), "Conformance should contain 'testModel'");
assertTrue(confItems.contains("testModel1"), "Conformance should contain 'testModel1'");
assertTrue(confItems.contains("testModel2"), "Conformance should contain 'testModel2'");
assertEquals(4, settings.findPath("testModel").intValue());
assertEquals(5, settings.findPath("testModel1").intValue());
assertEquals(6, settings.findPath("testModel2").findPath("value").intValue());
} catch (NullPointerException | ParseException | IOException ex) {
fail("Unexpected exception during test.", ex);
}
}

private JsonNode getRootUrl() throws JsonProcessingException, ParseException, IOException {
String urlString = serverSettings.getServiceUrl(version);
HTTPMethods.HttpResponse responseMap = HTTPMethods.doGet(serviceAdmin.getHttpClient(), urlString);
assertEquals(200, responseMap.code, () -> "Error fetching root URI: " + urlString);
return Utils.MAPPER.readTree(responseMap.response);
}

private void fetchForCode(String user, SensorThingsService service, URL link, int... codesWant) throws URISyntaxException {
HttpGet get = new HttpGet(link.toURI());
try (CloseableHttpResponse response = service.getHttpClient().execute(get)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import org.apache.http.Consts;
import org.apache.http.ParseException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
Expand Down Expand Up @@ -108,24 +109,39 @@ public static void logStats() {
* be empty.
*/
public static HttpResponse doGet(String urlString) {
HttpResponse result = null;
LOGGER.debug("Getting: {}", urlString);
countGet++;

try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet request = new HttpGet(urlString);
try (CloseableHttpResponse response = httpClient.execute(request)) {
result = new HttpResponse(response.getStatusLine().getStatusCode());
if (result.code == 200) {
result.setResponse(EntityUtils.toString(response.getEntity()));
} else {
result.setResponse("");
}
}
return doGet(httpClient, urlString);
} catch (IOException e) {
LOGGER.error("Exception: ", e);
}
return result;
return null;
}

/**
* Send HTTP GET request to the urlString, using the given HttpClient and
* return response code and response body
*
* @param httpClient the HttpClient to use.
* @param urlString The URL that the GET request should be sent to
* @return response-code and response(response body) of the HTTP GET in the
* MAP format. If the response is not 200, the response(response body) will
* be empty.
* @throws ParseException
* @throws IOException
*/
public static HttpResponse doGet(final CloseableHttpClient httpClient, String urlString) throws ParseException, IOException {
LOGGER.debug("Getting: {}", urlString);
countGet++;
HttpGet request = new HttpGet(urlString);
try (CloseableHttpResponse response = httpClient.execute(request)) {
HttpResponse result = new HttpResponse(response.getStatusLine().getStatusCode());
if (result.code == 200) {
result.setResponse(EntityUtils.toString(response.getEntity()));
} else {
result.setResponse("");
}
return result;
}
}

private static String responseToString(HttpURLConnection connection) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"conformance": ["testModel1"],
"testModel1": 5
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"conformance": ["testModel2"],
"testModel": {"value":6},
"testModel2": {"value":6}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ public final class ModelLoaderSettings implements ConfigDefaults {
public static final String TAG_SECURITY_PATH = PLUGIN_NAME + ".securityPath";
@DefaultValue("")
public static final String TAG_SECURITY_FILES = PLUGIN_NAME + ".securityFiles";
@DefaultValue("")
public static final String TAG_METADATA_DATA = PLUGIN_NAME + ".metadataData";
@DefaultValue("")
public static final String TAG_METADATA_PATH = PLUGIN_NAME + ".metadataPath";
@DefaultValue("")
public static final String TAG_METADATA_FILES = PLUGIN_NAME + ".metadataFiles";

public final String idTypeDefault;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
*/
package de.fraunhofer.iosb.ilt.frostserver.plugin.modelloader;

import static de.fraunhofer.iosb.ilt.frostserver.model.ext.TypeReferencesHelper.TYPE_REFERENCE_MAP;
import static de.fraunhofer.iosb.ilt.frostserver.plugin.modelloader.ModelLoaderSettings.PLUGIN_NAME;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.fraunhofer.iosb.ilt.frostserver.model.ModelRegistry;
import de.fraunhofer.iosb.ilt.frostserver.model.loader.DefEntityProperty;
Expand Down Expand Up @@ -47,6 +49,7 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
Expand All @@ -72,13 +75,20 @@ public class PluginModelLoader implements PluginRootDocument, PluginModel, Liqui
private List<DefModel> modelDefinitions = new ArrayList<>();
private Map<String, DefEntityProperty> primaryKeys;

private String liquibasePath;
private String modelPath;
private String securityPath;
private final List<String> conformance = new ArrayList<>();

private String liquibasePath;
private final List<String> liquibaseFiles = new ArrayList<>();

private String securityPath;
private final List<String> securityFiles = new ArrayList<>();

private String metadataPath;
private String metadataStringData;
private final List<String> metadataFiles = new ArrayList<>();
private Map<String, Object> metadataExtra;
private final List<String> conformance = new ArrayList<>();

public PluginModelLoader() {
LOGGER.info("Creating new ModelLoader Plugin.");
}
Expand All @@ -92,9 +102,9 @@ public void init(CoreSettings settings) {
idTypeDefault = pluginSettings.get(CoreModelSettings.TAG_ID_TYPE_DEFAULT, CoreModelSettings.class).toUpperCase();
settings.getPluginManager().registerPlugin(this);

liquibasePath = pluginSettings.get(ModelLoaderSettings.TAG_LIQUIBASE_PATH, ModelLoaderSettings.class);
String liquibaseString = pluginSettings.get(ModelLoaderSettings.TAG_LIQUIBASE_FILES, ModelLoaderSettings.class);
liquibaseFiles.addAll(Arrays.asList(StringUtils.split(liquibaseString.trim(), ", ")));
liquibasePath = pluginSettings.get(ModelLoaderSettings.TAG_LIQUIBASE_PATH, ModelLoaderSettings.class);

modelPath = pluginSettings.get(ModelLoaderSettings.TAG_MODEL_PATH, ModelLoaderSettings.class);
String modelFilesString = pluginSettings.get(ModelLoaderSettings.TAG_MODEL_FILES, ModelLoaderSettings.class);
Expand All @@ -112,6 +122,11 @@ public void init(CoreSettings settings) {
securityPath = pluginSettings.get(ModelLoaderSettings.TAG_SECURITY_PATH, ModelLoaderSettings.class);
String securityFilesString = pluginSettings.get(ModelLoaderSettings.TAG_SECURITY_FILES, ModelLoaderSettings.class);
securityFiles.addAll(Arrays.asList(StringUtils.split(securityFilesString.trim(), ", ")));

metadataStringData = pluginSettings.get(ModelLoaderSettings.TAG_METADATA_DATA, ModelLoaderSettings.class);
metadataPath = pluginSettings.get(ModelLoaderSettings.TAG_METADATA_PATH, ModelLoaderSettings.class);
String metadataFilesString = pluginSettings.get(ModelLoaderSettings.TAG_METADATA_FILES, ModelLoaderSettings.class);
metadataFiles.addAll(Arrays.asList(StringUtils.split(metadataFilesString.trim(), ", ")));
}
}

Expand Down Expand Up @@ -196,8 +211,78 @@ public void modifyServiceDocument(ServiceRequest request, Map<String, Object> re
// Nothing to add to.
return;
}
if (metadataExtra == null) {
metadataExtra = loadExtraMetadata();
}
Set<String> extensionList = (Set<String>) serverSettings.get(Service.KEY_CONFORMANCE_LIST);
extensionList.addAll(conformance);
mergeJson(serverSettings, metadataExtra);
}

private Map<String, Object> loadExtraMetadata() {
final ObjectMapper objectMapper = new ObjectMapper();
final Map<String, Object> extraMetadata = new LinkedHashMap<>();

// local copy for thread safety
final String localMetadataStringData = metadataStringData;
if (!StringHelper.isNullOrEmpty(localMetadataStringData)) {
try {
mergeJson(extraMetadata, objectMapper.readValue(localMetadataStringData, TYPE_REFERENCE_MAP));
} catch (JsonProcessingException ex) {
LOGGER.error("Failed to parse extra metadata.", ex);
}
// Free global String data.
metadataStringData = null;
}
for (String fileName : metadataFiles) {
mergeJson(extraMetadata, loadExtraMetadataFile(fileName));
}
return extraMetadata;
}

private void mergeJson(Map<String, Object> target, Map<String, Object> toMerge) {
if (toMerge == null) {
return;
}
for (var entry : toMerge.entrySet()) {
String name = entry.getKey();
Object valueToMerge = entry.getValue();
Object valueTarget = target.get(name);
if (valueTarget == null) {
target.put(name, valueToMerge);
} else if (valueTarget instanceof Map toMap && valueToMerge instanceof Map mergeMap) {
mergeJson(toMap, mergeMap);
} else if (valueTarget instanceof List toList && valueToMerge instanceof List mergeList) {
toList.addAll(mergeList);
} else if (valueTarget instanceof Set toSet && valueToMerge instanceof List mergeList) {
toSet.addAll(mergeList);
} else {
LOGGER.warn("Keeping value for {}, target already contains a value: {}; Ignoring {}", name, valueTarget, valueToMerge);
}
}
}

private Map<String, Object> loadExtraMetadataFile(String fileName) {
final Path fullPath = Path.of(metadataPath, fileName);
LOGGER.info("Loading extra landing page meta data from {}", fullPath.toAbsolutePath());
String data;
try {
ObjectMapper objectMapper = new ObjectMapper();
if (fullPath.toFile().exists()) {
data = new String(Files.readAllBytes(fullPath), StandardCharsets.UTF_8);
return objectMapper.readValue(data, TYPE_REFERENCE_MAP);
} else {
InputStream stream = getClass().getClassLoader().getResourceAsStream(fullPath.toString());
if (stream == null) {
LOGGER.info(" Not found: {}", fullPath);
} else {
return objectMapper.readValue(stream, TYPE_REFERENCE_MAP);
}
}
} catch (IOException ex) {
LOGGER.error("Failed to load extra metadata", ex);
}
return Collections.emptyMap();
}

@Override
Expand Down
6 changes: 6 additions & 0 deletions docs/settings/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ Therefore, we strongly recommend you contact us for support if your use case req
The file path where security definition files are located. This path is prepended to each entry in **plugins.modelLoader.securityFiles**.
* **plugins.modelLoader.securityFiles:** Since 2.2.0
A comma-separated list of security files to load. Each entry is prefixed with **plugins.modelLoader.securityPath**.
* **plugins.modelLoader.metadataData:** Since 2.3.2
A json-string containing extra data to add to the serverSettings object on the landing page. The added data is merged into the existing data.
* **plugins.modelLoader.metadataPath:** Since 2.3.2
The file path where extra metadata files are located. This path is prepended to each entry in **plugins.modelLoader.metadataFiles**.
* **plugins.modelLoader.metadataFiles:** Since 2.3.2
A comma-separated list of meta data files to load. Each entry is prefixed with **plugins.modelLoader.metadataPath**. Each file is loaded and its JSON-content is merged into the serverSettings object on the ROOT-URL.


## Response Format Plugins
Expand Down
Loading

0 comments on commit 789b23f

Please sign in to comment.