Skip to content
Open
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
Expand Up @@ -2,6 +2,8 @@

import com.contentgrid.appserver.actuator.policy.PolicyActuator;
import com.contentgrid.appserver.actuator.policy.PolicyVariables;
import com.contentgrid.appserver.actuator.webhooks.WebhookConfigActuator;
import com.contentgrid.appserver.actuator.webhooks.WebhookVariables;
import jakarta.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
Expand Down Expand Up @@ -41,6 +43,20 @@ PolicyActuator policyActuator(PolicyVariables policyVariables) {
return new PolicyActuator(applicationContext.getResource("classpath:rego/policy.rego"), policyVariables);
}

@Bean
WebhookVariables webhookVariables(ContentgridApplicationProperties applicationProperties) {
return WebhookVariables.builder()
.systemProperties(applicationProperties.getSystem())
.userVariables(applicationProperties.getVariables())
.build();
}

@Bean
WebhookConfigActuator webHooksConfigActuator(WebhookVariables webhookVariables) {
return new WebhookConfigActuator(applicationContext.getResource("classpath:eventhandler/webhooks.json"),
webhookVariables);
}

@Bean
@ConfigurationProperties(prefix = "contentgrid")
ContentgridApplicationProperties contentgridApplicationProperties() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.contentgrid.appserver.actuator.webhooks;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint;
import org.springframework.core.io.Resource;
import org.springframework.util.PropertyPlaceholderHelper;
import org.springframework.util.SystemPropertyUtils;

@WebEndpoint(id = "webhooks")
@RequiredArgsConstructor
public class WebhookConfigActuator {
private final Resource webhookResource;
private final WebhookVariables webhookVariables;
private static final PropertyPlaceholderHelper PROPERTY_PLACEHOLDER_HELPER = new PropertyPlaceholderHelper(
SystemPropertyUtils.PLACEHOLDER_PREFIX,
SystemPropertyUtils.PLACEHOLDER_SUFFIX
);

@ReadOperation(producesFrom = WebhookConfigProducible.class)
public String getConfig() throws IOException {
if (webhookResource.exists()) {
String contents = readContents(webhookResource);
return PROPERTY_PLACEHOLDER_HELPER.replacePlaceholders(contents, webhookVariables);
}
throw new FileNotFoundException("webhook config file at " + webhookResource.getDescription() + " is not present");
}

static String readContents(Resource resource) throws IOException {
try (InputStream resourceStream = resource.getInputStream()) {
return new String(resourceStream.readAllBytes(), StandardCharsets.UTF_8);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.contentgrid.appserver.actuator.webhooks;

import org.springframework.boot.actuate.endpoint.Producible;
import org.springframework.util.MimeType;

public enum WebhookConfigProducible implements Producible<WebhookConfigProducible> {

CONTENT_TYPE_WEBHOOK_CONFIG_V1 {
@Override
public MimeType getProducedMimeType() {
return MimeType.valueOf("application/vnd.contentgrid.webhooks.v1+json");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.contentgrid.appserver.actuator.webhooks;

import com.contentgrid.appserver.actuator.ContentgridApplicationProperties.SystemProperties;
import java.util.Map;
import lombok.Builder;
import lombok.Value;
import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver;

@Value
@Builder
public class WebhookVariables implements PlaceholderResolver {
SystemProperties systemProperties;
Map<String, String> userVariables;

@Override
public String resolvePlaceholder(String placeholderName) {
if (placeholderName.startsWith("vars.")) {
return userVariables.get(placeholderName.substring("vars.".length()));
}
switch(placeholderName) {
case "system.application.id":
return systemProperties.getApplicationId();
case "system.deployment.id":
return systemProperties.getDeploymentId();
default:
throw new IllegalArgumentException(String.format("Can not find a replacement for placeholder '%s'", placeholderName));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {
"contentgrid.system.policyPackage=xfb0e9318f3894300a64edba3532e6ac0",
"contentgrid.system.deploymentId=fb0e9318-f389-4300-a64e-dba3532e6ac0",
"contentgrid.system.applicationId=336d61a5-94cd-4b7a-b90b-369fbe2ef78c",
"management.endpoints.web.exposure.include=*",
"management.server.port=0" // random, different port from main port
}
Expand Down Expand Up @@ -50,4 +52,15 @@ void policyEndpointIsPublic() {
.isEqualTo("application/vnd.cncf.openpolicyagent.policy.layer.v1+rego;charset=UTF-8");
assertThat(resp.getBody()).contains("xfb0"); // templating works
}

@Test
void webhooksEndpointIsPublic() {
ResponseEntity<String> resp = rest.getForEntity("http://localhost:" + managementPort + "/actuator/webhooks", String.class);
assertThat(resp.getStatusCode().is2xxSuccessful()).isTrue();
assertThat(resp.getHeaders().getContentType().toString())
.isEqualTo("application/vnd.contentgrid.webhooks.v1+json");
// Check application id/deployment id templating
assertThat(resp.getBody()).contains("18-f3");
assertThat(resp.getBody()).contains("a5-94");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"webhooks":{"client":[{"filter": {"application_id": "${system.application.id}", "deployment_id": "${system.deployment.id}"}}]}}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ contentgrid:
system:
policyPackage: xfb0e9318f3894300a64edba3532e6ac0

# spring:
# rabbitmq:
# host: localhost
# port: 5672
# username: guest
# password: guest

# When unauthenticated, you get HTTP 403 instead of 401 if you don't have the following

#spring:
Expand Down
2 changes: 2 additions & 0 deletions contentgrid-appserver-autoconfigure/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dependencies {
compileOnly project(':contentgrid-appserver-contentstore-impl-fs')
compileOnly project(':contentgrid-appserver-contentstore-impl-s3')
compileOnly project(':contentgrid-appserver-domain')
compileOnly project(':contentgrid-appserver-events')
compileOnly project(':contentgrid-appserver-json-schema')
compileOnly project(':contentgrid-appserver-query-engine-api')
compileOnly project(':contentgrid-appserver-query-engine-impl-jooq')
Expand Down Expand Up @@ -48,6 +49,7 @@ dependencies {
testImplementation project(':contentgrid-appserver-contentstore-impl-fs')
testImplementation project(':contentgrid-appserver-contentstore-impl-s3')
testImplementation project(':contentgrid-appserver-domain')
testImplementation project(':contentgrid-appserver-events')
testImplementation project(':contentgrid-appserver-json-schema')
testImplementation project(':contentgrid-appserver-query-engine-api')
testImplementation project(':contentgrid-appserver-query-engine-impl-jooq')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.contentgrid.appserver.domain.ContentApi;
import com.contentgrid.appserver.domain.ContentApiImpl;
import com.contentgrid.appserver.domain.DatamodelApiImpl;
import com.contentgrid.appserver.domain.DomainEventDispatcher;
import com.contentgrid.appserver.domain.paging.cursor.CursorCodec;
import com.contentgrid.appserver.domain.paging.cursor.RequestIntegrityCheckCursorCodec;
import com.contentgrid.appserver.domain.paging.cursor.SimplePageBasedCursorCodec;
Expand All @@ -25,8 +26,9 @@ Clock clock() {
}

@Bean
DatamodelApiImpl datamodelApi(QueryEngine queryEngine, ContentStore contentStore, CursorCodec cursorCodec, Clock clock) {
return new DatamodelApiImpl(queryEngine, contentStore, cursorCodec, clock);
DatamodelApiImpl datamodelApi(QueryEngine queryEngine, ContentStore contentStore, DomainEventDispatcher dispatcher,
CursorCodec cursorCodec, Clock clock) {
return new DatamodelApiImpl(queryEngine, contentStore, dispatcher, cursorCodec, clock);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.contentgrid.appserver.autoconfigure.events;

import com.contentgrid.appserver.autoconfigure.query.engine.JOOQQueryEngineAutoConfiguration;
import com.contentgrid.appserver.domain.DomainEventDispatcher;
import com.contentgrid.appserver.domain.events.EntityFormatter;
import com.contentgrid.appserver.events.EventHandlerConfiguration;
import com.contentgrid.appserver.events.RabbitMqEventHandlers;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;

@AutoConfiguration(before = {
JOOQQueryEngineAutoConfiguration.class,
}, after = {
RabbitAutoConfiguration.class,
})
Comment on lines +15 to +19
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not have any conditional annotations that depend on RabbitAutoConfiguration having run, so that dependency is not necessary.

And the before ordering should probably be an after ordering on JOOQQueryEngineAutoConfiguration, because that is the configuration that has a dependency on this. Inverting the ordering like this is a bit confusing

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without after = RabbitAutoConfiguration, the ObjectProvider will always be empty.
With after = JOOQ instead of before, it will have a duplicate bean with the noopEventHandlers.

@ConditionalOnClass(EventHandlerConfiguration.class)
@Import(EventHandlerConfiguration.class)
public class ContentGridEventsAutoConfiguration {

@Bean
DomainEventDispatcher eventDispatcher(ObjectProvider<EntityFormatter> formatterProvider, ObjectProvider<RabbitMqEventHandlers> rabbitProvider) {
var formatter = formatterProvider.getIfAvailable();
var rabbit = rabbitProvider.getIfAvailable();
return new DomainEventDispatcher(formatter, rabbit);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.contentgrid.appserver.autoconfigure.query.engine;

import com.contentgrid.appserver.application.model.Application;
import com.contentgrid.appserver.application.model.values.ApplicationName;
import com.contentgrid.appserver.application.model.values.EntityName;
import com.contentgrid.appserver.autoconfigure.json.schema.ApplicationResolverAutoConfiguration;
import com.contentgrid.appserver.query.engine.api.QueryEngine;
import com.contentgrid.appserver.query.engine.api.TableCreator;
import com.contentgrid.appserver.query.engine.api.data.EntityData;
import com.contentgrid.appserver.query.engine.jooq.JOOQQueryEngine;
import com.contentgrid.appserver.query.engine.jooq.JOOQTableCreator;
import com.contentgrid.appserver.query.engine.jooq.TransactionalQueryEngine;
Expand Down Expand Up @@ -41,7 +44,8 @@ JOOQCountStrategy jooqTimedCountStrategy(@Value("${contentgrid.appserver.query-e
}

@Bean
QueryEngine jooqQueryEngine(DSLContextResolver dslContextResolver, JOOQCountStrategy countStrategy, PlatformTransactionManager transactionManager) {
QueryEngine jooqQueryEngine(DSLContextResolver dslContextResolver, JOOQCountStrategy countStrategy,
PlatformTransactionManager transactionManager) {
return new TransactionalQueryEngine(new JOOQQueryEngine(dslContextResolver, countStrategy), transactionManager);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.contentgrid.appserver.autoconfigure.rest;

import com.contentgrid.appserver.autoconfigure.events.ContentGridEventsAutoConfiguration;
import com.contentgrid.appserver.rest.ContentGridRestFormatterConfiguration;
import com.contentgrid.appserver.rest.EntityRestController;
import com.contentgrid.appserver.rest.assembler.EntityDataRepresentationModelAssembler;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.context.annotation.Import;
import org.springframework.hateoas.RepresentationModel;

@AutoConfiguration(before = {ContentGridEventsAutoConfiguration.class})
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({EntityRestController.class, RepresentationModel.class, EntityDataRepresentationModelAssembler.class})
@Import(ContentGridRestFormatterConfiguration.class)
public class ContentGridRestFormatterAutoConfiguration {
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ com.contentgrid.appserver.autoconfigure.contentstore.EncryptedContentStoreAutoCo
com.contentgrid.appserver.autoconfigure.contentstore.FilesystemContentStoreAutoConfiguration
com.contentgrid.appserver.autoconfigure.contentstore.S3ContentStoreAutoConfiguration
com.contentgrid.appserver.autoconfigure.domain.ContentGridDomainAutoConfiguration
com.contentgrid.appserver.autoconfigure.events.ContentGridEventsAutoConfiguration
com.contentgrid.appserver.autoconfigure.flyway.FlywayPostgresAutoConfiguration
com.contentgrid.appserver.autoconfigure.json.schema.ApplicationResolverAutoConfiguration
com.contentgrid.appserver.autoconfigure.query.engine.JOOQQueryEngineAutoConfiguration
com.contentgrid.appserver.autoconfigure.rest.ContentGridRestAutoConfiguration
com.contentgrid.appserver.autoconfigure.rest.ContentGridRestFormatterAutoConfiguration
com.contentgrid.appserver.autoconfigure.actuator.ContentgridActuatorAutoConfiguration
com.contentgrid.appserver.autoconfigure.security.DefaultSecurityAutoConfiguration
com.contentgrid.appserver.autoconfigure.webjars.WebjarsRestAutoConfiguration
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
import com.contentgrid.appserver.application.model.values.ApplicationName;
import com.contentgrid.appserver.autoconfigure.contentstore.FilesystemContentStoreAutoConfiguration;
import com.contentgrid.appserver.autoconfigure.domain.ContentGridDomainAutoConfiguration;
import com.contentgrid.appserver.autoconfigure.events.ContentGridEventsAutoConfiguration;
import com.contentgrid.appserver.autoconfigure.query.engine.JOOQQueryEngineAutoConfiguration;
import com.contentgrid.appserver.registry.ApplicationResolver;
import com.contentgrid.appserver.registry.SingleApplicationResolver;
import com.contentgrid.appserver.rest.EntityRestController;
import com.contentgrid.thunx.api.autoconfigure.AbacContextAutoConfiguration;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration;
Expand Down Expand Up @@ -42,7 +44,10 @@ class ContentGridRestAutoConfigurationTest {
FilesystemContentStoreAutoConfiguration.class,
// autoconfiguration for domain
ContentGridDomainAutoConfiguration.class,
ContentGridEventsAutoConfiguration.class,
ContentGridRestFormatterAutoConfiguration.class,
// autoconfigurations for rest
HttpMessageConvertersAutoConfiguration.class,
WebMvcAutoConfiguration.class,
AbacContextAutoConfiguration.class,
ContentGridRestAutoConfiguration.class
Expand Down
1 change: 1 addition & 0 deletions contentgrid-appserver-domain/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ dependencies {
api project(':contentgrid-appserver-application-model')
api project(':contentgrid-appserver-query-engine-api')
api 'com.contentgrid.hateoas:contentgrid-pagination-api'
api 'com.fasterxml.jackson.core:jackson-databind'

implementation 'org.slf4j:slf4j-api'
implementation project(':contentgrid-appserver-contentstore-api')
Expand Down
Loading