Skip to content

Commit

Permalink
feat: content property now uses setAutoGrowNestedPaths when setting c… (
Browse files Browse the repository at this point in the history
#1507)

* feat: content property now uses setAutoGrowNestedPaths when setting content to provide better support for null embedded content objects

- filesystem storage only

* test: add @Embedded content tests for azure storage

* test: add @Embedded content tests for gcp storage

* test: add @Embedded content tests for s3 storage

Fixes #1480
  • Loading branch information
paulcwarren committed Jul 14, 2023
1 parent 885b19a commit 394780c
Show file tree
Hide file tree
Showing 11 changed files with 452 additions and 193 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -493,11 +493,12 @@ public S unsetContent(S entity, PropertyPath propertyPath, UnsetContentParams pa
}

// reset content fields
property.setContentId(entity, null, new org.springframework.content.commons.mappingcontext.Condition() {
@Override
public boolean matches(TypeDescriptor descriptor) {
for (Annotation annotation : descriptor.getAnnotations()) {
if ("jakarta.persistence.Id".equals(
if (resource != null) {
property.setContentId(entity, null, new org.springframework.content.commons.mappingcontext.Condition() {
@Override
public boolean matches(TypeDescriptor descriptor) {
for (Annotation annotation : descriptor.getAnnotations()) {
if ("jakarta.persistence.Id".equals(
annotation.annotationType().getCanonicalName())
|| "org.springframework.data.annotation.Id"
.equals(annotation.annotationType()
Expand All @@ -509,7 +510,8 @@ public boolean matches(TypeDescriptor descriptor) {
}
});

property.setContentLength(entity, 0);
property.setContentLength(entity, 0);
}

return entity;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,14 @@
package internal.org.springframework.content.azure.it;

import static com.github.paulcwarren.ginkgo4j.Ginkgo4jDSL.*;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;

import com.azure.storage.blob.specialized.BlockBlobClient;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import javax.sql.DataSource;

import com.azure.core.http.rest.PagedIterable;
import com.azure.storage.blob.BlobContainerClient;
import com.azure.storage.blob.BlobServiceClientBuilder;
import com.azure.storage.blob.models.BlobItem;
import com.github.paulcwarren.ginkgo4j.Ginkgo4jConfiguration;
import com.github.paulcwarren.ginkgo4j.Ginkgo4jRunner;
import jakarta.persistence.*;
import lombok.*;
import net.bytebuddy.utility.RandomString;
import org.apache.commons.io.IOUtils;
import org.hamcrest.Matchers;
import org.junit.Assert;
Expand Down Expand Up @@ -51,19 +38,19 @@
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import com.azure.core.http.rest.PagedIterable;
import com.azure.storage.blob.BlobContainerClient;
import com.azure.storage.blob.BlobServiceClientBuilder;
import com.azure.storage.blob.models.BlobItem;
import com.github.paulcwarren.ginkgo4j.Ginkgo4jConfiguration;
import com.github.paulcwarren.ginkgo4j.Ginkgo4jRunner;
import javax.sql.DataSource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.bytebuddy.utility.RandomString;
import static com.github.paulcwarren.ginkgo4j.Ginkgo4jDSL.*;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;

@RunWith(Ginkgo4jRunner.class)
@Ginkgo4jConfiguration(threads=1)
Expand All @@ -90,6 +77,9 @@ public class AzureStorageIT {
private TestEntityRepository repo;
private TestEntityStore store;

private EmbeddedRepository embeddedRepo;
private EmbeddedStore embeddedStore;

private String resourceLocation;

{
Expand All @@ -103,6 +93,9 @@ public class AzureStorageIT {
repo = context.getBean(TestEntityRepository.class);
store = context.getBean(TestEntityStore.class);

embeddedRepo = context.getBean(EmbeddedRepository.class);
embeddedStore = context.getBean(EmbeddedStore.class);

RandomString random = new RandomString(5);
resourceLocation = random.nextString();
});
Expand Down Expand Up @@ -521,6 +514,29 @@ public class AzureStorageIT {
// });
// });

Context("@Embedded content", () -> {
Context("given a entity with a null embedded content object", () -> {
It("should return null when content is fetched", () -> {
EntityWithEmbeddedContent entity = embeddedRepo.save(new EntityWithEmbeddedContent());
assertThat(embeddedStore.getContent(entity, PropertyPath.from("content")), is(nullValue()));
});

It("should be successful when content is set", () -> {
EntityWithEmbeddedContent entity = embeddedRepo.save(new EntityWithEmbeddedContent());
embeddedStore.setContent(entity, PropertyPath.from("content"), new ByteArrayInputStream("Hello Spring Content World!".getBytes()));
try (InputStream is = embeddedStore.getContent(entity, PropertyPath.from("content"))) {
assertThat(IOUtils.contentEquals(is, new ByteArrayInputStream("Hello Spring Content World!".getBytes())), is(true));
}
});

It("should return null when content is unset", () -> {
EntityWithEmbeddedContent entity = embeddedRepo.save(new EntityWithEmbeddedContent());
EntityWithEmbeddedContent expected = new EntityWithEmbeddedContent(entity.getId(), entity.getContent());
assertThat(embeddedStore.unsetContent(entity, PropertyPath.from("content")), is(expected));
int i = 0;
});
});
});
});
});
}
Expand Down Expand Up @@ -637,5 +653,33 @@ public interface SharedIdStore extends ContentStore<SharedIdContentIdEntity, Str
//
// public interface SharedSpringIdRepository extends JpaRepository<SharedSpringIdContentIdEntity, String> {}
// public interface SharedSpringIdStore extends ContentStore<SharedSpringIdContentIdEntity, String> {}

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name="entity_with_embedded")
public static class EntityWithEmbeddedContent {

@Id
private String id = UUID.randomUUID().toString();

@Embedded
private EmbeddedContent content;
}

@Embeddable
@NoArgsConstructor
@Data
public static class EmbeddedContent {

@ContentId
private String contentId;

@ContentLength
private Long contentLen;
}

public interface EmbeddedRepository extends JpaRepository<EntityWithEmbeddedContent, String> {}
public interface EmbeddedStore extends ContentStore<EntityWithEmbeddedContent, String> {}
}
// EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.NullValueInNestedPathException;
import org.springframework.core.convert.TypeDescriptor;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.springframework.util.Assert;

@Getter
@Setter
Expand All @@ -16,21 +18,26 @@ public class ContentProperty {

private String contentPropertyPath;
private String contentIdPropertyPath;
private TypeDescriptor contentIdType;
private String contentLengthPropertyPath;
private String mimeTypePropertyPath;
private String originalFileNamePropertyPath;

public Object getCustomProperty(Object entity, String propertyName) {
String customContentPropertyPath = contentPropertyPath + StringUtils.capitalize(propertyName);

BeanWrapper wrapper = new BeanWrapperImpl(entity);
return wrapper.getPropertyValue(customContentPropertyPath);
BeanWrapper wrapper = getBeanWrapperForRead(entity);
try {
return wrapper.getPropertyValue(customContentPropertyPath);
} catch (NullValueInNestedPathException nvinpe) {
return null;
}
}

public void setCustomProperty(Object entity, String propertyName, Object value) {
String customContentPropertyPath = contentPropertyPath + StringUtils.capitalize(propertyName);

BeanWrapper wrapper = new BeanWrapperImpl(entity);
BeanWrapper wrapper = getBeanWrapperForWrite(entity);
wrapper.setPropertyValue(customContentPropertyPath, value);
}

Expand All @@ -39,16 +46,20 @@ public Object getContentId(Object entity) {
return null;
}

BeanWrapper wrapper = new BeanWrapperImpl(entity);
return wrapper.getPropertyValue(contentIdPropertyPath);
BeanWrapper wrapper = getBeanWrapperForRead(entity);
try {
return wrapper.getPropertyValue(contentIdPropertyPath);
} catch (NullValueInNestedPathException nvinpe) {
return null;
}
}

public void setContentId(Object entity, Object value, Condition condition) {
if (contentIdPropertyPath == null) {
return;
}

BeanWrapper wrapper = new BeanWrapperImpl(entity);
BeanWrapper wrapper = getBeanWrapperForWrite(entity);

if (condition != null) {
TypeDescriptor t = wrapper.getPropertyTypeDescriptor(contentIdPropertyPath);
Expand All @@ -61,29 +72,38 @@ public void setContentId(Object entity, Object value, Condition condition) {
}

public TypeDescriptor getContentIdType(Object entity) {
if (contentIdPropertyPath == null) {
return null;
}
Assert.notNull(this.contentIdType, "content id property type must be set");
return this.contentIdType;
}

BeanWrapper wrapper = new BeanWrapperImpl(entity);
return wrapper.getPropertyTypeDescriptor(contentIdPropertyPath);
public TypeDescriptor getContentIdType() {
Assert.notNull(this.contentIdType, "content id property type must be set");
return this.contentIdType;
}

public void setContentIdType(TypeDescriptor descriptor) {
this.contentIdType = descriptor;
}

public Object getContentLength(Object entity) {
if (contentLengthPropertyPath == null) {
return 0L;
}

BeanWrapper wrapper = new BeanWrapperImpl(entity);
return wrapper.getPropertyValue(contentLengthPropertyPath);
BeanWrapper wrapper = getBeanWrapperForRead(entity);
try {
return wrapper.getPropertyValue(contentLengthPropertyPath);
} catch (NullValueInNestedPathException nvinpe) {
return null;
}
}

public void setContentLength(Object entity, Object value) {
if (contentLengthPropertyPath == null) {
return;
}

BeanWrapper wrapper = new BeanWrapperImpl(entity);
BeanWrapper wrapper = getBeanWrapperForWrite(entity);
wrapper.setPropertyValue(contentLengthPropertyPath, value);
}

Expand All @@ -92,16 +112,20 @@ public Object getMimeType(Object entity) {
return null;
}

BeanWrapper wrapper = new BeanWrapperImpl(entity);
return wrapper.getPropertyValue(mimeTypePropertyPath);
BeanWrapper wrapper = getBeanWrapperForRead(entity);
try {
return wrapper.getPropertyValue(mimeTypePropertyPath);
} catch (NullValueInNestedPathException nvinpe) {
return null;
}
}

public void setMimeType(Object entity, Object value) {
if (mimeTypePropertyPath == null) {
return;
}

BeanWrapper wrapper = new BeanWrapperImpl(entity);
BeanWrapper wrapper = getBeanWrapperForWrite(entity);
wrapper.setPropertyValue(mimeTypePropertyPath, value);
}

Expand All @@ -110,7 +134,7 @@ public void setOriginalFileName(Object entity, Object value) {
return;
}

BeanWrapper wrapper = new BeanWrapperImpl(entity);
BeanWrapper wrapper = getBeanWrapperForWrite(entity);
wrapper.setPropertyValue(originalFileNamePropertyPath, value);
}

Expand All @@ -119,7 +143,22 @@ public Object getOriginalFileName(Object entity) {
return null;
}

BeanWrapper wrapper = getBeanWrapperForRead(entity);
try {
return wrapper.getPropertyValue(originalFileNamePropertyPath);
} catch (NullValueInNestedPathException nvinpe) {
return null;
}
}

private BeanWrapper getBeanWrapperForRead(Object entity) {
BeanWrapper wrapper = new BeanWrapperImpl(entity);
return wrapper;
}

private BeanWrapper getBeanWrapperForWrite(Object entity) {
BeanWrapper wrapper = new BeanWrapperImpl(entity);
return wrapper.getPropertyValue(originalFileNamePropertyPath);
wrapper.setAutoGrowNestedPaths(true);
return wrapper;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.springframework.content.commons.annotations.ContentLength;
import org.springframework.content.commons.annotations.MimeType;
import org.springframework.content.commons.annotations.OriginalFileName;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.util.StringUtils;

import lombok.Getter;
Expand Down Expand Up @@ -82,6 +83,7 @@ public boolean visitClassEnd(String path, Class<?> klazz) {
if (property.getContentIdPropertyPath() != null) {
contentProperty.setContentPropertyPath(property.getContentPropertyPath());
contentProperty.setContentIdPropertyPath(property.getContentIdPropertyPath());
contentProperty.setContentIdType(property.getContentIdType());
}
if (property.getContentLengthPropertyPath() != null) {
contentProperty.setContentLengthPropertyPath(property.getContentLengthPropertyPath());
Expand Down Expand Up @@ -122,6 +124,7 @@ public boolean visitField(String path, Class<?> klazz, Field f) {
}
updateContentProperty(property::setContentPropertyPath, fullyQualify(path, this.propertyName(f.getName()), this.getContentPropertySeparator()));
updateContentProperty(property::setContentIdPropertyPath, fullyQualify(path, f.getName(), this.getContentPropertySeparator()));
property.setContentIdType(TypeDescriptor.valueOf(f.getType()));
}
} else if (f.isAnnotationPresent(ContentLength.class)) {
LOGGER.trace(String.format("%s.%s is @ContentLength", f.getDeclaringClass().getCanonicalName(), f.getName()));
Expand Down
Loading

0 comments on commit 394780c

Please sign in to comment.