Skip to content

Commit

Permalink
Merge pull request #644 from paulcwarren/fix/issue-638
Browse files Browse the repository at this point in the history
Allow converter to be configured and use when fetching entities where appropriate
  • Loading branch information
paulcwarren authored Oct 15, 2021
2 parents 84117b0 + 824b39e commit cf4616e
Show file tree
Hide file tree
Showing 4 changed files with 254 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.springframework.content.rest.config.RestConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.repository.support.Repositories;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.support.WebDataBinderFactory;
Expand Down Expand Up @@ -85,7 +86,7 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m
if (AssociativeStore.class.isAssignableFrom(info.getInterface())) {

if (entityUriTemplate.matches(pathInfo)) {
Object entity = new EntityResolver(context, this.getRepositories(), info, pathSegments)
Object entity = new EntityResolver(context, this.getRepositories(), info, pathSegments, (ConversionService)config.converters())
.resolve(entityUriTemplate.match(pathInfo));

return this.resolveAssociativeStoreEntityArgument(info, entity);
Expand All @@ -95,7 +96,7 @@ else if (entityPropertyUriTemplate.matches(pathInfo)) {

Map<String,String> variables = entityPropertyUriTemplate.match(pathInfo);

Object domainObj = new EntityResolver(context, this.getRepositories(), info, pathSegments)
Object domainObj = new EntityResolver(context, this.getRepositories(), info, pathSegments, (ConversionService)config.converters())
.resolve(variables);

HttpMethod method = HttpMethod.valueOf(webRequest.getNativeRequest(HttpServletRequest.class).getMethod());
Expand All @@ -109,7 +110,7 @@ else if (entityPropertyWithIdUriTemplate.matches(pathInfo)) {

Map<String,String> variables = entityPropertyWithIdUriTemplate.match(pathInfo);

Object domainObj = new EntityResolver(context, this.getRepositories(), info, pathSegments)
Object domainObj = new EntityResolver(context, this.getRepositories(), info, pathSegments, (ConversionService)config.converters())
.resolve(variables);

HttpMethod method = HttpMethod.valueOf(webRequest.getNativeRequest(HttpServletRequest.class).getMethod());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.security.Principal;
Expand Down Expand Up @@ -31,7 +32,8 @@
import org.springframework.content.commons.storeservice.StoreInfo;
import org.springframework.context.ApplicationContext;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.data.querydsl.binding.QuerydslPredicate;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.support.Repositories;
Expand Down Expand Up @@ -64,12 +66,15 @@ public class EntityResolver {
private Repositories repositories;
private StoreInfo storeInfo;
private String[] pathSegments;
private ConversionService converters;

public EntityResolver(ApplicationContext context, Repositories repositories, StoreInfo storeInfo, String[] pathSegments) {

public EntityResolver(ApplicationContext context, Repositories repositories, StoreInfo storeInfo, String[] pathSegments, ConversionService converters) {
this.context = context;
this.repositories = repositories;
this.storeInfo = storeInfo;
this.pathSegments = pathSegments;
this.converters = converters;
}

public Object resolve(Map<String,String> variables) {
Expand Down Expand Up @@ -105,7 +110,7 @@ public Object findOne(Repositories repositories, String repository, String id)
return findOne(repositories, domainObjClazz, id);
}

public Object findOne(Repositories repositories, Class<?> domainObjClass, String id)
public Object findOne(Repositories repositories, Class<?> domainObjClass, Serializable id)
throws HttpRequestMethodNotSupportedException {

Optional<Object> domainObj = null;
Expand All @@ -118,31 +123,46 @@ public Object findOne(Repositories repositories, Class<?> domainObjClass, String
if (invoker != null) {
domainObj = invoker.invokeFindById(id);
}
} catch (ConverterNotFoundException e) {

domainObj = findOneByReflection(repositories, domainObjClass, id);
} catch (Exception e) {

e.printStackTrace();
}
} else {

RepositoryInformation ri = RepositoryUtils.findRepositoryInformation(repositories, domainObjClass);
domainObj = findOneByReflection(repositories, domainObjClass, id);
}
return domainObj.orElseThrow(ResourceNotFoundException::new);
}

if (ri == null) {
throw new ResourceNotFoundException();
}
@SuppressWarnings("unchecked")
public Optional<Object> findOneByReflection(Repositories repositories, Class<?> domainObjClass, Serializable id)
throws HttpRequestMethodNotSupportedException {

Class<?> domainObjClazz = ri.getDomainType();
Class<?> idClazz = ri.getIdType();
RepositoryInformation ri = RepositoryUtils.findRepositoryInformation(repositories, domainObjClass);

Optional<Method> findOneMethod = ri.getCrudMethods().getFindOneMethod();
if (!findOneMethod.isPresent()) {
throw new HttpRequestMethodNotSupportedException("fineOne");
}
if (ri == null) {
throw new ResourceNotFoundException();
}

Class<?> domainObjClazz = ri.getDomainType();
Class<?> idClazz = ri.getIdType();

Object oid = new DefaultConversionService().convert(id, idClazz);
domainObj = (Optional<Object>) ReflectionUtils.invokeMethod(findOneMethod.get(),
repositories.getRepositoryFor(domainObjClazz).get(),
oid);
Optional<Method> findOneMethod = ri.getCrudMethods().getFindOneMethod();
if (!findOneMethod.isPresent()) {
throw new HttpRequestMethodNotSupportedException("fineOne");
}
return domainObj.orElseThrow(ResourceNotFoundException::new);

Serializable oid = id;
if (converters.canConvert(String.class, idClazz)) {
oid = (Serializable) converters.convert(id, idClazz);
}

return (Optional<Object>) ReflectionUtils.invokeMethod(findOneMethod.get(),
repositories.getRepositoryFor(domainObjClazz).get(),
oid);
}

private RepositoryInvoker resolveRootResourceInformation(StoreInfo info, String[] pathSegments, ModelAndViewContainer mavContainer, WebDataBinderFactory binderFactory)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.ConverterRegistry;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.io.Resource;
import org.springframework.data.repository.support.DefaultRepositoryInvokerFactory;
import org.springframework.data.repository.support.Repositories;
Expand Down Expand Up @@ -48,6 +50,7 @@ public class RestConfiguration implements InitializingBean {
private URI baseUri = NO_URI;
private StoreCorsRegistry corsRegistry;
private boolean fullyQualifiedLinks = FULLY_QUALIFIED_DEFAULTS_DEFAULT;
private ConverterRegistry converters = new DefaultConversionService();

private Map<Class<?>, DomainTypeConfig> domainTypeConfigMap = new HashMap<>();

Expand Down Expand Up @@ -92,6 +95,10 @@ public DomainTypeConfig forDomainType(Class<?> type) {
return config;
}

public ConverterRegistry converters() {
return converters;
}

@Bean
Stores stores() {
return new StoresImpl(context);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
package it.rest.embeddedid;

import static com.github.paulcwarren.ginkgo4j.Ginkgo4jDSL.BeforeEach;
import static com.github.paulcwarren.ginkgo4j.Ginkgo4jDSL.Describe;
import static com.github.paulcwarren.ginkgo4j.Ginkgo4jDSL.It;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.Serializable;
import java.nio.file.Files;
import java.util.UUID;

import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.content.commons.annotations.ContentId;
import org.springframework.content.commons.annotations.ContentLength;
import org.springframework.content.commons.annotations.MimeType;
import org.springframework.content.fs.config.EnableFilesystemStores;
import org.springframework.content.fs.io.FileSystemResourceLoader;
import org.springframework.content.fs.store.FilesystemContentStore;
import org.springframework.content.rest.config.ContentRestConfigurer;
import org.springframework.content.rest.config.RestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import com.github.paulcwarren.ginkgo4j.Ginkgo4jConfiguration;
import com.github.paulcwarren.ginkgo4j.Ginkgo4jSpringRunner;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@RunWith(Ginkgo4jSpringRunner.class)
@Ginkgo4jConfiguration(threads = 1)
@SpringBootTest(classes = {EmbeddedIdTest.Application.class},
webEnvironment=WebEnvironment.RANDOM_PORT)
@EnableAutoConfiguration(exclude = { HypermediaAutoConfiguration.class, SecurityAutoConfiguration.class })
public class EmbeddedIdTest {

@LocalServerPort
private int serverPort;

@Autowired
private TestEntityRepository repo;

@Autowired
private TestEntityContentRepository store;

@Autowired
private WebApplicationContext context;

private MockMvc mvc;

{
Describe("EmbeddedId", () -> {

BeforeEach(() -> {
mvc = MockMvcBuilders.webAppContextSetup(context).build();
});

It("should have a content handler mapping bean", () -> {

String content = "this is some content";

TestEntity entity = repo.save(new TestEntity());
entity = store.setContent(entity, new ByteArrayInputStream(content.getBytes()));
entity = repo.save(entity);

// String command = "curl -H 'Accept: text/plain' http://localhost:" + serverPort + "/testEntities/" + entity.getId().toString();
// Process process = Runtime.getRuntime().exec(command);
// while (process.isAlive() == true) {
// Thread.sleep(1000);
// }
// assertThat(process.exitValue(), is(0));
// assertThat(IOUtils.toString(process.getInputStream()), is(content));

MockHttpServletResponse response =
mvc.perform(
get("/testEntities/" + entity.getId()).
accept("text/plain")).
andExpect(status().isOk()).
andReturn().getResponse();

assertThat(response, is(not(nullValue())));
assertThat(response.getContentAsString(), is(content));

});
});
}

@SpringBootApplication
@EnableJpaRepositories(considerNestedRepositories = true, basePackages={"it.rest.embeddedid"})
@EnableFilesystemStores(basePackages = "it.rest.embeddedid")
@Import({RestConfiguration.class})
public static class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

@Bean
public FileSystemResourceLoader filesystemRoot() throws IOException {
return new FileSystemResourceLoader(Files.createTempDirectory("").toFile().getAbsolutePath());
}

@Bean
public ContentRestConfigurer configureConversionService() {
return new ContentRestConfigurer() {

@Override
public void configure(RestConfiguration config) {

config.converters().addConverter(new Converter<String, TestEntityId>() {
@Override
public TestEntityId convert(String source) {
String[] segments = source.split("_");
return new TestEntityId(segments[0], segments[1]);
}
});
}
};
}
}

@Entity
@Getter
@Setter
@ToString
public static class TestEntity {

@EmbeddedId
private TestEntityId id = new TestEntityId();

@ContentId
@Column(name = "content_id")
private String contentId;

@ContentLength
@Column(name = "content_length")
private long contentLength;

@MimeType
@Column(name = "mime_type")
private String mimeType = "text/plain";
}

public static class TestEntityId implements Serializable {

private static final long serialVersionUID = -1710555467685181030L;

private String first;

private String last;

public TestEntityId() {
this.first = UUID.randomUUID().toString();
this.last = UUID.randomUUID().toString();
}

public TestEntityId(String mediumId, String fileId) {
this.first = mediumId;
this.last = fileId;
}

@Override
public String toString() {
return first + "_" + last;
}
}

public interface TestEntityRepository extends JpaRepository<TestEntity, TestEntityId> {
}

public interface TestEntityContentRepository extends FilesystemContentStore<TestEntity, TestEntityId> {
}

@Test
public void noop() {}
}

0 comments on commit cf4616e

Please sign in to comment.