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

Send events when creating entities in multipart post #2070

Merged
merged 1 commit into from
Aug 9, 2024
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
Expand Up @@ -18,9 +18,12 @@
import org.springframework.content.commons.storeservice.StoreInfo;
import org.springframework.content.commons.storeservice.Stores;
import org.springframework.content.rest.config.RestConfiguration;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.data.repository.support.RepositoryInvokerFactory;
import org.springframework.data.rest.core.event.AfterCreateEvent;
import org.springframework.data.rest.core.event.BeforeCreateEvent;
import org.springframework.data.rest.core.mapping.ResourceType;
import org.springframework.data.rest.core.support.SelfLinkProvider;
import org.springframework.data.rest.webmvc.*;
Expand Down Expand Up @@ -61,9 +64,10 @@ public class RepositoryEntityMultipartController {
private Stores stores;
private SelfLinkProvider selfLinkProvider;
private HttpHeadersPreparer headersPreparer;
private ApplicationEventPublisher publisher;

@Autowired
public RepositoryEntityMultipartController(RestConfiguration restConfig, RepositoryInvokerFactory repoInvokerFactory, ContentPropertyToRequestMappingContext requestMappingContext, SelfLinkProvider selfLinkProvider, Stores stores, MappingContext mappingContext, ContentPropertyToExportedContext exportedMappingContext, StoreByteRangeHttpRequestHandler byteRangeRestRequestHandler, @Qualifier("entityMultipartHttpMessageConverterConfigurer") RepositoryRestConfigurer configurer, HttpHeadersPreparer headersPreparer) {
public RepositoryEntityMultipartController(RestConfiguration restConfig, RepositoryInvokerFactory repoInvokerFactory, ContentPropertyToRequestMappingContext requestMappingContext, SelfLinkProvider selfLinkProvider, Stores stores, MappingContext mappingContext, ContentPropertyToExportedContext exportedMappingContext, StoreByteRangeHttpRequestHandler byteRangeRestRequestHandler, @Qualifier("entityMultipartHttpMessageConverterConfigurer") RepositoryRestConfigurer configurer, HttpHeadersPreparer headersPreparer, ApplicationEventPublisher publisher) {
this.restConfig = restConfig;
this.repoInvokerFactory = repoInvokerFactory;
this.requestMappingContext = requestMappingContext;
Expand All @@ -73,6 +77,7 @@ public RepositoryEntityMultipartController(RestConfiguration restConfig, Reposit
this.exportedMappingContext = exportedMappingContext;
this.byteRangeRestRequestHandler = byteRangeRestRequestHandler;
this.headersPreparer = headersPreparer;
this.publisher = publisher;
}

@SuppressWarnings({ "rawtypes", "unchecked" })
Expand All @@ -97,9 +102,14 @@ public ResponseEntity<RepresentationModel<?>> createEntityAndContent(RootResourc

String store = pathSegments[1];

BeforeCreateEvent beforeCreate = new BeforeCreateEvent(savedEntity);
publisher.publishEvent(beforeCreate);
// Save the entity and re-assign the result to savedEntity, so that it exists in the repository before content is added to it.
savedEntity = repoInvokerFactory.getInvokerFor(domainType).invokeSave(savedEntity);

AfterCreateEvent afterCreate = new AfterCreateEvent(savedEntity);
publisher.publishEvent(afterCreate);

StoreInfo info = this.stores.getStore(Store.class, StoreUtils.withStorePath(store));
if (info != null) {
ContentStoreContentService service = new ContentStoreContentService(restConfig, info, repoInvokerFactory.getInvokerFor(domainType), mappingContext, exportedMappingContext, byteRangeRestRequestHandler);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package internal.org.springframework.content.rest.support;

import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.event.AbstractRepositoryEventListener;

@Configuration
public class EventListenerConfig {

@Bean
TestEventListener testListener() {
return new TestEventListener();
}

public static class TestEventListener extends AbstractRepositoryEventListener<Object> {
private final List<Object> beforeCreate = new ArrayList<>();
private final List<Object> afterCreate = new ArrayList<>();

@Override
protected void onBeforeCreate(Object entity) {
beforeCreate.add(entity);
}

@Override
protected void onAfterCreate(Object entity) {
afterCreate.add(entity);
}

public void clear() {
this.afterCreate.clear();
this.beforeCreate.clear();
}

public List<Object> getBeforeCreate() {
return beforeCreate;
}

public List<Object> getAfterCreate() {
return afterCreate;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.github.paulcwarren.ginkgo4j.Ginkgo4jConfiguration;
import internal.org.springframework.content.rest.support.EventListenerConfig.TestEventListener;
import java.io.ByteArrayInputStream;
import java.nio.charset.Charset;
import java.util.Optional;
Expand Down Expand Up @@ -35,15 +37,16 @@
import com.github.paulcwarren.ginkgo4j.Ginkgo4jSpringRunner;

@RunWith(Ginkgo4jSpringRunner.class)
// @Ginkgo4jConfiguration(threads=1)
@Ginkgo4jConfiguration(threads=1)
@WebAppConfiguration
@ContextConfiguration(classes = {
StoreConfig.class,
EntityConfig.class,
DelegatingWebMvcConfiguration.class,
RepositoryRestMvcConfiguration.class,
RestConfiguration.class,
HypermediaConfiguration.class
HypermediaConfiguration.class,
EventListenerConfig.class
})
@Transactional
@ActiveProfiles("store")
Expand Down Expand Up @@ -88,6 +91,9 @@ public class ContentEntityRestEndpointsIT {
@Autowired
TestStore store;

@Autowired
TestEventListener eventListener;

@Autowired
private WebApplicationContext context;

Expand Down Expand Up @@ -430,6 +436,29 @@ public class ContentEntityRestEndpointsIT {
});
});

Context("given a multipart/form POST and an event listener", () -> {
BeforeEach(() -> {
eventListener.clear();
});
It("should create a new entity and fire the onBeforeCreate/onAfterCreate events", () -> {

// POST the entity
MockHttpServletResponse response = mvc.perform(multipart("/testEntity3s")
.param("name", "foo foo")
.param("hidden", "bar bar")
.param("ying", "yang")
.param("things", "one", "two"))

.andExpect(status().isCreated())
.andReturn().getResponse();

assertThat(eventListener.getBeforeCreate().size(), is(1));
assertThat(eventListener.getAfterCreate().size(), is(1));
assertThat(((TestEntity3) eventListener.getBeforeCreate().get(0)).getName(), is("foo foo"));
assertThat(((TestEntity3) eventListener.getAfterCreate().get(0)).getName(), is("foo foo"));
});
});

Context("given a multipart/form POST to an entity with a mapped content property", () -> {
It("should create a new entity and its content and respond with a 201 Created", () -> {
// assert content does not exist
Expand Down
Loading