Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

manual url for internal forms and override support #2673

Merged
merged 7 commits into from
Aug 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.bakdata.conquery.models.config;

import java.net.MalformedURLException;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import com.bakdata.conquery.io.cps.CPSType;
import com.bakdata.conquery.util.validation.ManualURI;
import lombok.Getter;
import lombok.Setter;

/**
* Configuration of manual links for {@link org.checkerframework.checker.signature.qual.InternalForm} and
* overriding of other {@link com.bakdata.conquery.apiv1.forms.Form}s.
*/
@CPSType(id = "MANUAL", base = PluginConfig.class)
@Getter
@Setter
public class ManualConfig implements PluginConfig {


/**
* Maps a {@link CPSType} of a {@link com.bakdata.conquery.apiv1.forms.Form} to a manual URL.
* E.g.:
* <ul>
* <li>EXPORT_FORM -> https://example.org/export-form</li>
* <li>FULL_EXPORT_FORM -> ./full-export-form</li>
* </ul>
*/
private Map<String, @ManualURI URI> forms = Collections.emptyMap();

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.io.PrintWriter;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
Expand All @@ -15,6 +16,7 @@
import com.bakdata.conquery.io.cps.CPSTypeIdResolver;
import com.bakdata.conquery.models.config.ConqueryConfig;
import com.bakdata.conquery.models.config.FrontendConfig;
import com.bakdata.conquery.models.config.ManualConfig;
import com.bakdata.conquery.resources.admin.rest.AdminProcessor;
import com.bakdata.conquery.util.QueryUtils;
import com.fasterxml.jackson.databind.JsonNode;
Expand Down Expand Up @@ -117,13 +119,32 @@ private Map<String, FormType> generateFEFormConfigMap() {
continue;
}

// Make relative handbook URLs relative to configured handbook base
final JsonNode manualUrl = configTree.get(MANUAL_URL_KEY);
// Make relative manual URLs relative to configured handbook base
// Config url mappings override urls from frontend config jsons
final URI manualURL = config.getPluginConfig(ManualConfig.class)
// first check override
.map(ManualConfig::getForms)
.map(m -> m.get(fullTypeIdentifier))
// then query the frontend config json
.orElseGet(() -> {
final JsonNode manualUrl = configTree.get(MANUAL_URL_KEY);
if (manualUrl == null) {
// Nothing specified, skip
return null;
}
if (!manualUrl.isTextual()) {
throw new IllegalArgumentException(
String.format("FrontendFormConfig %s contained field 'manualUrl' but it was not a text. Was: '%s'.", fullTypeIdentifier, manualUrl.getNodeType()));
}

return URI.create(manualUrl.textValue());
});
final URL manualBaseUrl = config.getFrontend().getManualUrl();
if (manualBaseUrl != null && manualUrl != null) {
final TextNode manualNode = relativizeManualUrl(fullTypeIdentifier, manualUrl, manualBaseUrl);
if (manualBaseUrl != null && manualURL != null) {

final TextNode manualNode = relativizeManualUrl(fullTypeIdentifier, manualURL, manualBaseUrl);
if (manualNode == null) {
log.warn("Manual url relativiation did not succeed for {}. Skipping registration.", fullTypeIdentifier);
log.warn("Manual url relativization did not succeed for {}. Skipping registration.", fullTypeIdentifier);
continue;
}
configTree.set(MANUAL_URL_KEY, manualNode);
Expand All @@ -137,26 +158,26 @@ private Map<String, FormType> generateFEFormConfigMap() {
return result.build();
}

private TextNode relativizeManualUrl(@NonNull String formTypeIdentifier, @NonNull JsonNode manualUrl, @NonNull URL manualBaseUrl) {
if (!manualUrl.isTextual()) {
log.warn("FrontendFormConfig {} contained field 'manualUrl' but it was not a text. Was: '{}'.", formTypeIdentifier, manualUrl.getNodeType());
return null;
}

final String urlString = manualUrl.textValue();
final URI manualUri = URI.create(urlString);
if (manualUri.isAbsolute()) {
log.trace("Manual url for {} was already absolute: {}. Skipping relativization.", formTypeIdentifier, manualUri);
return new TextNode(urlString);
}
private TextNode relativizeManualUrl(@NonNull String formTypeIdentifier, @NonNull URI manualUri, @NonNull URL manualBaseUrl) {

try {
final String absoluteUrl = manualBaseUrl.toURI().resolve(manualUri).toString();
log.trace("Computed manual url for {}: {}", formTypeIdentifier, absoluteUrl);
return new TextNode(absoluteUrl);
if (manualUri.isAbsolute()) {
log.trace("Manual url for {} was already absolute: {}. Skipping relativization.", formTypeIdentifier, manualUri);
return new TextNode(manualUri.toURL().toString());
}

try {
final String absoluteUrl = manualBaseUrl.toURI().resolve(manualUri).toURL().toString();
log.trace("Computed manual url for {}: {}", formTypeIdentifier, absoluteUrl);
return new TextNode(absoluteUrl);
}
catch (URISyntaxException e) {
log.warn("Unable to resolve manual base url ('{}') and relative manual url ('{}')", manualBaseUrl, manualUri, e);
return null;
}
}
catch (URISyntaxException e) {
log.warn("Unable to resolve manual base url ('{}') and relative manual url ('{}')", manualBaseUrl, manualUri, e);
catch (MalformedURLException e) {
log.error("Unable to build url", e);
return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.bakdata.conquery.util.validation;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

@Target({ANNOTATION_TYPE, FIELD, TYPE_USE})
@Retention(RUNTIME)
@Constraint(validatedBy = ManualURIValidator.class)
@Documented
public @interface ManualURI {
String message() default "";

Class<?>[] groups() default {};

@SuppressWarnings("UnusedDeclaration") Class<? extends Payload>[] payload() default {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.bakdata.conquery.util.validation;

import java.net.MalformedURLException;
import java.net.URI;
import java.util.List;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class ManualURIValidator implements ConstraintValidator<ManualURI, URI> {

@Override
public boolean isValid(URI value, ConstraintValidatorContext context) {

if (value == null) {
return true;
}

context.disableDefaultConstraintViolation();

boolean isValid = true;

if (value.isOpaque()) {
isValid = false;
context.buildConstraintViolationWithTemplate("The URI was opaque")
.addConstraintViolation();
}

if (value.isAbsolute()) {
try {
var unused = value.toURL();
}
catch (MalformedURLException e) {
isValid = false;
context.buildConstraintViolationWithTemplate("Absolute URI is not a valid URL (" + e.getMessage() + ")")
.addConstraintViolation();
}
if (!List.of("http", "https").contains(value.getScheme())) {
isValid = false;
context.buildConstraintViolationWithTemplate("The URI has an unsupported Scheme: " + value.getScheme())
.addConstraintViolation();
}
}
else {
if (value.getAuthority() != null) {
isValid = false;
context.buildConstraintViolationWithTemplate("The URI is not absolute but has an authority: " + value.getAuthority())
.addConstraintViolation();
}
}

return isValid;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ public void getConfigs() {
mode3.setForm(form);
mode3.setFeatures(List.of(new CQConcept()));

TestForm form3 = new TestForm();
TestForm form3 = new TestForm.Abs();

ObjectMapper mapper = FormConfigProcessor.getMAPPER();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,7 @@
import com.bakdata.conquery.models.query.Visitable;
import com.bakdata.conquery.models.worker.DatasetRegistry;

/**
* Two {@link CPSType} are used here to reference two different
* frontend form configs (see com/bakdata/conquery/frontend/forms/test_form_*_url.frontend_conf.json).
*/
@CPSType(id = "TEST_FORM_REL_URL", base = QueryDescription.class)
@CPSType(id = "TEST_FORM_ABS_URL", base = QueryDescription.class)
public class TestForm extends Form {
public abstract class TestForm extends Form {

@Override
public ManagedExecution<?> toManagedExecution(User user, Dataset submittedDataset) {
Expand Down Expand Up @@ -56,4 +50,12 @@ public String getLocalizedTypeLabel() {
public void visit(Consumer<Visitable> visitor) {
visitor.accept(this);
}

@CPSType(id = "TEST_FORM_ABS_URL", base = QueryDescription.class)
public static class Abs extends TestForm {
}

@CPSType(id = "TEST_FORM_REL_URL", base = QueryDescription.class)
public static class Rel extends TestForm {
}
}
16 changes: 15 additions & 1 deletion docs/Config JSON.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,20 @@ A `PluginConfig` is used to define settings for Conquery plugins.
Different types of PluginConfig can be used by setting `type` to one of the following values:


### MANUAL<sup><sub><sup> [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/ManualConfig.java#L17-L20)</sup></sub></sup>
Configuration of manual links for {@link org.checkerframework.checker.signature.qual.InternalForm} and overriding of other {@link com.bakdata.conquery.apiv1.forms.Form}s.

<details><summary>Details</summary><p>

Java Type: `com.bakdata.conquery.models.config.ManualConfig`

Supported Fields:

| | Field | Type | Default | Example | Description |
| --- | --- | --- | --- | --- | --- |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/ManualConfig.java#L27-L34) | forms | map from `String` to `@ManualURI URI` | | | Maps a {@link CPSType} of a {@link com.bakdata.conquery.apiv1.forms.Form} to a manual URL. E.g.: <ul> <li>EXPORT_FORM -> https://example.org/export-form</li> <li>FULL_EXPORT_FORM -> ./full-export-form</li> </ul> |
</p></details>



---
Expand Down Expand Up @@ -229,7 +243,7 @@ Supported Fields:
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/ConqueryConfig.java#L104) | jerseyClient | `@Valid @NotNull JerseyClientConfiguration` | | | |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/ConqueryConfig.java#L64) | locale | [LocaleConfig](#Type-LocaleConfig) | | | |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/ConqueryConfig.java#L87) | metricsConfig | `ConqueryMetricsConfig` | | | |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/ConqueryConfig.java#L108) | plugins | list of `PluginConfig` | `[]` | | |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/ConqueryConfig.java#L108) | plugins | list of [PluginConfig](#Base-PluginConfig) | `[]` | | |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/ConqueryConfig.java#L44) | preprocessor | [PreprocessingConfig](#Type-PreprocessingConfig) | | | |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/ConqueryConfig.java#L73) | queries | [QueryConfig](#Type-QueryConfig) | | | |
| [✎](https://github.com/bakdata/conquery/edit/develop/backend/src/main/java/com/bakdata/conquery/models/config/ConqueryConfig.java#L54-L56) | resultProviders | list of `ResultRendererProvider` | | | The order of this lists determines the ordner of the generated result urls in a query status. |
Expand Down