diff --git a/metacat-metadata/src/functionalTest/java/com/netflix/metacat/metadata/store/data/package-info.java b/metacat-metadata/src/functionalTest/java/com/netflix/metacat/metadata/store/data/package-info.java new file mode 100644 index 000000000..64d9e02d4 --- /dev/null +++ b/metacat-metadata/src/functionalTest/java/com/netflix/metacat/metadata/store/data/package-info.java @@ -0,0 +1,22 @@ +/* + * + * Copyright 2021 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * Metacat Metadata test classes. + */ +package com.netflix.metacat.metadata.store.data; diff --git a/metacat-metadata/src/functionalTest/java/com/netflix/metacat/metadata/store/data/repositories/CrdbDataMetadataRepositoryTests.java b/metacat-metadata/src/functionalTest/java/com/netflix/metacat/metadata/store/data/repositories/CrdbDataMetadataRepositoryTests.java new file mode 100644 index 000000000..04b5fd5a7 --- /dev/null +++ b/metacat-metadata/src/functionalTest/java/com/netflix/metacat/metadata/store/data/repositories/CrdbDataMetadataRepositoryTests.java @@ -0,0 +1,18 @@ +//CHECKSTYLE:OFF +package com.netflix.metacat.metadata.store.data.repositories; + +import com.netflix.metacat.metadata.store.configs.UserMetadataStoreConfig; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureDataJpa; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.transaction.annotation.Transactional; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = {UserMetadataStoreConfig.class}) +@ActiveProfiles(profiles = {"usermetadata-crdb"}) +@Transactional +@AutoConfigureDataJpa +public class CrdbDataMetadataRepositoryTests extends DataMetadataRepositoryTests { +} diff --git a/metacat-metadata/src/functionalTest/java/com/netflix/metacat/metadata/store/data/repositories/CrdbDefinitionMetadataRepositoryTests.java b/metacat-metadata/src/functionalTest/java/com/netflix/metacat/metadata/store/data/repositories/CrdbDefinitionMetadataRepositoryTests.java new file mode 100644 index 000000000..6872cd2d2 --- /dev/null +++ b/metacat-metadata/src/functionalTest/java/com/netflix/metacat/metadata/store/data/repositories/CrdbDefinitionMetadataRepositoryTests.java @@ -0,0 +1,18 @@ +//CHECKSTYLE:OFF +package com.netflix.metacat.metadata.store.data.repositories; + +import com.netflix.metacat.metadata.store.configs.UserMetadataStoreConfig; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureDataJpa; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.transaction.annotation.Transactional; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = {UserMetadataStoreConfig.class}) +@ActiveProfiles(profiles = {"usermetadata-crdb"}) +@Transactional +@AutoConfigureDataJpa +public class CrdbDefinitionMetadataRepositoryTests extends DefinitionMetadataRepositoryTests { +} diff --git a/metacat-metadata/src/functionalTest/java/com/netflix/metacat/metadata/store/data/repositories/package-info.java b/metacat-metadata/src/functionalTest/java/com/netflix/metacat/metadata/store/data/repositories/package-info.java new file mode 100644 index 000000000..40d777810 --- /dev/null +++ b/metacat-metadata/src/functionalTest/java/com/netflix/metacat/metadata/store/data/repositories/package-info.java @@ -0,0 +1,22 @@ +/* + * + * Copyright 2021 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * Metacat Metadata repository test classes. + */ +package com.netflix.metacat.metadata.store.data.repositories; diff --git a/metacat-metadata/src/functionalTest/resources/application-usermetadata-crdb.yml b/metacat-metadata/src/functionalTest/resources/application-usermetadata-crdb.yml new file mode 100644 index 000000000..767b7da19 --- /dev/null +++ b/metacat-metadata/src/functionalTest/resources/application-usermetadata-crdb.yml @@ -0,0 +1,23 @@ +spring: + datasource: + platform: crdb + url: jdbc:postgresql://127.0.0.1:26257/defaultdb?sslmode=disable + username: admin + password: + schema: classpath:/crdb/schema.sql + initialization-mode: always + driverClassName: org.postgresql.Driver + hikari: + connection-timeout: 5000 # 5 seconds + max-lifetime: 580000 # 9 minutes 40 seconds + data-source-properties: + loginTimeout: 5 # 5 seconds + socketTimeout: 30 # 30 seconds + ApplicationName: polaris + + jpa: + hibernate: + ddl-auto: none + properties: + hibernate: + dialect: org.hibernate.dialect.PostgreSQLDialect diff --git a/metacat-metadata/src/functionalTest/resources/crdb/schema.sql b/metacat-metadata/src/functionalTest/resources/crdb/schema.sql new file mode 100644 index 000000000..7e243602a --- /dev/null +++ b/metacat-metadata/src/functionalTest/resources/crdb/schema.sql @@ -0,0 +1,30 @@ +drop table if exists definition_metadata; +drop table if exists data_metadata; + +create table definition_metadata ( + id UUID NOT NULL DEFAULT gen_random_uuid(), + name STRING(1024) not null, + data JSON, + version INT not null, + is_deleted BOOL default FALSE not null, + created_by STRING(255) not null, + created_date TIMESTAMP not null, + last_updated_by STRING(255) not null, + last_updated_date TIMESTAMP not null, + primary key (id), + constraint definition_metadata_uq unique (name) +); + +create table data_metadata ( + id UUID NOT NULL DEFAULT gen_random_uuid(), + uri STRING(4096) not null, + data JSON, + version INT not null, + is_deleted BOOL default FALSE not null, + created_by STRING(255) not null, + created_date TIMESTAMP not null, + last_updated_by STRING(255) not null, + last_updated_date TIMESTAMP not null, + primary key (id), + constraint data_metadata_uq unique (uri) +); \ No newline at end of file diff --git a/metacat-metadata/src/main/java/com/netflix/metacat/metadata/UserMetadataServiceImpl.java b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/UserMetadataServiceImpl.java new file mode 100644 index 000000000..bd12bb3d4 --- /dev/null +++ b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/UserMetadataServiceImpl.java @@ -0,0 +1,40 @@ +package com.netflix.metacat.metadata; + +import com.netflix.metacat.common.json.MetacatJson; +import com.netflix.metacat.common.server.properties.Config; +import com.netflix.metacat.common.server.usermetadata.BaseUserMetadataService; +import com.netflix.metacat.common.server.usermetadata.MetadataInterceptor; +import com.netflix.metacat.metadata.store.UserMetadataStoreService; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * The Hibernate-based User metadata service implementation. + * + * @author rveeramacheneni + */ +public class UserMetadataServiceImpl extends BaseUserMetadataService { + + private final UserMetadataStoreService userMetadataStoreService; + private final MetacatJson metacatJson; + private final Config config; + private final MetadataInterceptor metadataInterceptor; + + /** + * Ctor. + * + * @param userMetadataStoreService The User metadata store service. + * @param metacatJson The Metacat jackson JSON mapper. + * @param config The config. + * @param metadataInterceptor The metadata interceptor. + */ + @Autowired + public UserMetadataServiceImpl(final UserMetadataStoreService userMetadataStoreService, + final MetacatJson metacatJson, + final Config config, + final MetadataInterceptor metadataInterceptor) { + this.userMetadataStoreService = userMetadataStoreService; + this.metacatJson = metacatJson; + this.config = config; + this.metadataInterceptor = metadataInterceptor; + } +} diff --git a/metacat-metadata/src/main/java/com/netflix/metacat/metadata/package-info.java b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/package-info.java index af2929fff..be6583213 100644 --- a/metacat-metadata/src/main/java/com/netflix/metacat/metadata/package-info.java +++ b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/package-info.java @@ -17,6 +17,6 @@ */ /** - * Metacat Metadata classes. + * Metacat User metadata classes. */ package com.netflix.metacat.metadata; diff --git a/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/MetadataConnector.java b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/MetadataConnector.java deleted file mode 100644 index 60878b559..000000000 --- a/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/MetadataConnector.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.netflix.metacat.metadata.store; - -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; - -/** - * Contains a set of methods to do CRUD operations on metadata. - */ -@RequiredArgsConstructor(onConstructor = @__(@Autowired)) -public class MetadataConnector { - -} diff --git a/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/UserMetadataStoreService.java b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/UserMetadataStoreService.java new file mode 100644 index 000000000..2832e889a --- /dev/null +++ b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/UserMetadataStoreService.java @@ -0,0 +1,32 @@ +package com.netflix.metacat.metadata.store; + +import com.netflix.metacat.metadata.store.data.repositories.DataMetadataRepository; +import com.netflix.metacat.metadata.store.data.repositories.DefinitionMetadataRepository; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Storage interface for user metadata entity operations. + * + * @author rveeramacheneni + */ +@Slf4j +public class UserMetadataStoreService { + + private final DefinitionMetadataRepository definitionMetadataRepository; + private final DataMetadataRepository dataMetadataRepository; + + /** + * Ctor. + * + * @param definitionMetadataRepository The definition metadata repository. + * @param dataMetadataRepository The data metadata repository. + */ + @Autowired + public UserMetadataStoreService(@NonNull final DefinitionMetadataRepository definitionMetadataRepository, + @NonNull final DataMetadataRepository dataMetadataRepository) { + this.definitionMetadataRepository = definitionMetadataRepository; + this.dataMetadataRepository = dataMetadataRepository; + } +} diff --git a/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/configs/UserMetadataStoreConfig.java b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/configs/UserMetadataStoreConfig.java new file mode 100644 index 000000000..110d7143a --- /dev/null +++ b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/configs/UserMetadataStoreConfig.java @@ -0,0 +1,48 @@ +package com.netflix.metacat.metadata.store.configs; + +import com.netflix.metacat.common.json.MetacatJson; +import com.netflix.metacat.common.json.MetacatJsonLocator; +import com.netflix.metacat.metadata.store.UserMetadataStoreService; +import com.netflix.metacat.metadata.store.data.repositories.DataMetadataRepository; +import com.netflix.metacat.metadata.store.data.repositories.DefinitionMetadataRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +/** + * The user metadata store config. + * + * @author rveeramacheneni + */ +@Configuration +@EntityScan("com.netflix.metacat.metadata.store.data.*") +@EnableJpaRepositories("com.netflix.metacat.metadata.store.data.*") +public class UserMetadataStoreConfig { + + /** + * The user metadata store service. + * + * @param definitionMetadataRepository The definition metadata repository. + * @param dataMetadataRepository The data metadata repository. + * @return the constructed bean. + */ + @Bean + public UserMetadataStoreService userMetadataStoreService( + final DefinitionMetadataRepository definitionMetadataRepository, + final DataMetadataRepository dataMetadataRepository) { + return new UserMetadataStoreService(definitionMetadataRepository, dataMetadataRepository); + } + + /** + * Store metacat JSON Handler. + * + * @return The JSON handler + */ + @Bean + @ConditionalOnMissingBean(MetacatJson.class) + public MetacatJson metacatJson() { + return new MetacatJsonLocator(); + } +} diff --git a/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/configs/package-info.java b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/configs/package-info.java new file mode 100644 index 000000000..a44c7435a --- /dev/null +++ b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/configs/package-info.java @@ -0,0 +1,22 @@ +/* + * + * Copyright 2021 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * User metadata store config classes. + */ +package com.netflix.metacat.metadata.store.configs; diff --git a/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/converters/ObjectNodeConverter.java b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/converters/ObjectNodeConverter.java new file mode 100644 index 000000000..d4b9ab09b --- /dev/null +++ b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/converters/ObjectNodeConverter.java @@ -0,0 +1,40 @@ +package com.netflix.metacat.metadata.store.data.converters; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.netflix.metacat.common.json.MetacatJson; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +/** + * Attribute converter for Jackson ObjectNode type. + * + * @author rveeramacheneni + */ +@Slf4j +@Converter(autoApply = true) +@SuppressWarnings("PMD") +public class ObjectNodeConverter implements AttributeConverter { + private final MetacatJson metacatJson; + + /** + * Ctor. + * + * @param metacatJson the Jackson object mapper. + */ + public ObjectNodeConverter(@NonNull final MetacatJson metacatJson) { + this.metacatJson = metacatJson; + } + + @Override + public String convertToDatabaseColumn(final ObjectNode attribute) { + return attribute == null ? null : attribute.toString(); + } + + @Override + public ObjectNode convertToEntityAttribute(final String dbData) { + return dbData == null ? null : metacatJson.parseJsonObject(dbData); + } +} diff --git a/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/converters/QualifiedNameConverter.java b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/converters/QualifiedNameConverter.java new file mode 100644 index 000000000..b0fe271d2 --- /dev/null +++ b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/converters/QualifiedNameConverter.java @@ -0,0 +1,28 @@ +package com.netflix.metacat.metadata.store.data.converters; + +import com.netflix.metacat.common.QualifiedName; +import lombok.extern.slf4j.Slf4j; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +/** + * The attribute converter for the QualifiedName type. + * + * @author rveeramacheneni + */ +@Slf4j +@Converter(autoApply = true) +@SuppressWarnings("PMD") +public class QualifiedNameConverter implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(final QualifiedName attribute) { + return attribute == null ? null : attribute.toString(); + } + + @Override + public QualifiedName convertToEntityAttribute(final String dbData) { + return dbData == null ? null : QualifiedName.fromString(dbData); + } +} diff --git a/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/converters/package-info.java b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/converters/package-info.java new file mode 100644 index 000000000..6b9b02899 --- /dev/null +++ b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/converters/package-info.java @@ -0,0 +1,22 @@ +/* + * + * Copyright 2021 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * Metacat Metadata converter classes. + */ +package com.netflix.metacat.metadata.store.data.converters; diff --git a/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/entities/AuditEntity.java b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/entities/AuditEntity.java new file mode 100644 index 000000000..09ff61c69 --- /dev/null +++ b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/entities/AuditEntity.java @@ -0,0 +1,57 @@ +package com.netflix.metacat.metadata.store.data.entities; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.annotation.LastModifiedDate; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Embeddable; +import java.time.Instant; + +/** + * Embeddable entity with audit fields. + * + * @author rveeramacheneni + */ +@Embeddable +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@ToString(of = { + "createdBy", + "lastModifiedBy", + "createdDate", + "lastModifiedDate" +}) +public class AuditEntity { + + @Basic + @Column(name = "created_by", nullable = false) + @CreatedBy + protected String createdBy; + + @Basic + @Column(name = "last_updated_by") + @LastModifiedBy + protected String lastModifiedBy; + + @Basic + @Column(name = "created_date", updatable = false) + @CreatedDate + protected Instant createdDate; + + @Basic + @Column(name = "last_updated_date") + @LastModifiedDate + protected Instant lastModifiedDate; +} diff --git a/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/entities/BaseEntity.java b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/entities/BaseEntity.java new file mode 100644 index 000000000..40f0b4614 --- /dev/null +++ b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/entities/BaseEntity.java @@ -0,0 +1,57 @@ +package com.netflix.metacat.metadata.store.data.entities; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Embedded; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; +import javax.persistence.Version; + +/** + * Represents a basic metadata entity. + * + * @author rveeramacheneni + */ +@MappedSuperclass +@Getter +@Setter +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(of = "id") +@ToString(of = { + "id", + "version", + "audit" +}) +@SuppressWarnings("PMD") +public abstract class BaseEntity { + + @Basic + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false, unique = true, updatable = false) + @Setter(AccessLevel.NONE) + protected String id; + + @Version + @Column(name = "version") + @Setter(AccessLevel.NONE) + protected Long version; + + @Embedded + @Builder.Default + protected AuditEntity audit = new AuditEntity(); +} diff --git a/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/entities/BaseUserMetadataEntity.java b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/entities/BaseUserMetadataEntity.java new file mode 100644 index 000000000..f546ddf93 --- /dev/null +++ b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/entities/BaseUserMetadataEntity.java @@ -0,0 +1,44 @@ +package com.netflix.metacat.metadata.store.data.entities; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.netflix.metacat.metadata.store.data.converters.ObjectNodeConverter; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.SuperBuilder; +import org.hibernate.annotations.ColumnDefault; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.MappedSuperclass; + +/** + * Represents a basic user metadata entity. + * + * @author rveeramacheneni + */ +@MappedSuperclass +@Getter +@Setter +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@ToString(callSuper = true, of = { + "isDeleted" +}) +@SuppressWarnings("PMD") +public class BaseUserMetadataEntity extends BaseEntity { + + @Basic + @Column(name = "is_deleted", nullable = false) + @ColumnDefault("false") + protected boolean isDeleted; + + @Basic + @Column(name = "data", columnDefinition = "jsonb") + @Convert(converter = ObjectNodeConverter.class) + protected ObjectNode data; +} diff --git a/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/entities/DataMetadataEntity.java b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/entities/DataMetadataEntity.java new file mode 100644 index 000000000..74c16da51 --- /dev/null +++ b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/entities/DataMetadataEntity.java @@ -0,0 +1,35 @@ +package com.netflix.metacat.metadata.store.data.entities; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * Represents a data metadata entity. + * + * @author rveeramacheneni + */ +@Entity +@Getter +@Setter +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@ToString(of = { + "uri" +}) +@Table(name = "data_metadata") +public class DataMetadataEntity extends BaseUserMetadataEntity { + + @Basic + @Column(name = "uri", nullable = false, unique = true) + private String uri; +} diff --git a/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/entities/DefinitionMetadataEntity.java b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/entities/DefinitionMetadataEntity.java new file mode 100644 index 000000000..3a7bdfa3b --- /dev/null +++ b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/entities/DefinitionMetadataEntity.java @@ -0,0 +1,40 @@ +package com.netflix.metacat.metadata.store.data.entities; + +import com.netflix.metacat.common.QualifiedName; +import com.netflix.metacat.metadata.store.data.converters.QualifiedNameConverter; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * The definition metadata entity. + * + * @author rveeramacheneni + */ +@Entity +@Getter +@Setter +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@ToString(of = { + "name" +}) +@Table(name = "definition_metadata") +@SuppressWarnings("PMD") +public class DefinitionMetadataEntity extends BaseUserMetadataEntity { + + @Basic + @Column(name = "name", nullable = false, unique = true) + @Convert(converter = QualifiedNameConverter.class) + private QualifiedName name; +} diff --git a/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/entities/package-info.java b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/entities/package-info.java new file mode 100644 index 000000000..92032fe37 --- /dev/null +++ b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/entities/package-info.java @@ -0,0 +1,22 @@ +/* + * + * Copyright 2021 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * Metacat Metadata entity classes. + */ +package com.netflix.metacat.metadata.store.data.entities; diff --git a/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/repositories/DataMetadataRepository.java b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/repositories/DataMetadataRepository.java new file mode 100644 index 000000000..59b4d3dec --- /dev/null +++ b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/repositories/DataMetadataRepository.java @@ -0,0 +1,23 @@ +package com.netflix.metacat.metadata.store.data.repositories; + +import com.netflix.metacat.metadata.store.data.entities.DataMetadataEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +/** + * The data metadata entity repository. + * + * @author rveeramacheneni + */ +@Repository +public interface DataMetadataRepository extends JpaRepository { + /** + * Find a data metadata entity using the given uri. + * + * @param uri The uri of the entity. + * @return The data metadata entity. + */ + Optional findByUri(String uri); +} diff --git a/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/repositories/DefinitionMetadataRepository.java b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/repositories/DefinitionMetadataRepository.java new file mode 100644 index 000000000..7c3fba89d --- /dev/null +++ b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/repositories/DefinitionMetadataRepository.java @@ -0,0 +1,22 @@ +package com.netflix.metacat.metadata.store.data.repositories; + +import com.netflix.metacat.common.QualifiedName; +import com.netflix.metacat.metadata.store.data.entities.DefinitionMetadataEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +/** + * The DefinitionMetadata entity repository. + */ +@Repository +public interface DefinitionMetadataRepository extends JpaRepository { + /** + * Find a definition metadata entity using the given QualifiedName. + * + * @param name The QualifiedName of the entity. + * @return The definition metadata entity. + */ + Optional findByName(QualifiedName name); +} diff --git a/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/repositories/package-info.java b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/repositories/package-info.java new file mode 100644 index 000000000..54a4e9b37 --- /dev/null +++ b/metacat-metadata/src/main/java/com/netflix/metacat/metadata/store/data/repositories/package-info.java @@ -0,0 +1,22 @@ +/* + * + * Copyright 2021 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * Metacat Metadata repository classes. + */ +package com.netflix.metacat.metadata.store.data.repositories; diff --git a/metacat-metadata/src/test/java/com/netflix/metacat/metadata/package-info.java b/metacat-metadata/src/test/java/com/netflix/metacat/metadata/package-info.java index ff92d7022..bb59aac79 100644 --- a/metacat-metadata/src/test/java/com/netflix/metacat/metadata/package-info.java +++ b/metacat-metadata/src/test/java/com/netflix/metacat/metadata/package-info.java @@ -20,3 +20,4 @@ * Metacat metadata test classes. */ package com.netflix.metacat.metadata; + diff --git a/metacat-metadata/src/test/java/com/netflix/metacat/metadata/store/data/package-info.java b/metacat-metadata/src/test/java/com/netflix/metacat/metadata/store/data/package-info.java new file mode 100644 index 000000000..693605eef --- /dev/null +++ b/metacat-metadata/src/test/java/com/netflix/metacat/metadata/store/data/package-info.java @@ -0,0 +1,22 @@ +/* + * + * Copyright 2021 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * Metacat metadata test classes. + */ +package com.netflix.metacat.metadata.store.data; diff --git a/metacat-metadata/src/test/java/com/netflix/metacat/metadata/store/data/repositories/DataMetadataRepositoryTests.java b/metacat-metadata/src/test/java/com/netflix/metacat/metadata/store/data/repositories/DataMetadataRepositoryTests.java new file mode 100644 index 000000000..f5f740c3b --- /dev/null +++ b/metacat-metadata/src/test/java/com/netflix/metacat/metadata/store/data/repositories/DataMetadataRepositoryTests.java @@ -0,0 +1,59 @@ +//CHECKSTYLE:OFF +package com.netflix.metacat.metadata.store.data.repositories; + +import com.netflix.metacat.metadata.store.configs.UserMetadataStoreConfig; +import com.netflix.metacat.metadata.store.data.entities.DataMetadataEntity; +import com.netflix.metacat.metadata.util.EntityTestUtil; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureDataJpa; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.util.Assert; + +import javax.transaction.Transactional; +import java.util.Optional; + +/** + * Test data metadata repository APIs + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = {UserMetadataStoreConfig.class}) +@ActiveProfiles(profiles = {"usermetadata-h2db"}) +@Transactional +@AutoConfigureDataJpa +@Slf4j +public class DataMetadataRepositoryTests { + + @Autowired + public DataMetadataRepository dataMetadataRepository; + + @Test + public void testCreateAndGet() { + DataMetadataEntity metadataEntity = + dataMetadataRepository.save(EntityTestUtil.createDataMetadataEntity()); + + // get the entity back + DataMetadataEntity savedEntity = dataMetadataRepository.getOne(metadataEntity.getId()); + Assert.isTrue(savedEntity.equals(metadataEntity), "Retrieved entity should be the same"); + String testUri = savedEntity.getUri(); + log.info("Found test metadata entity Uri: {} and Id: {}", + testUri, savedEntity.getId()); + + // soft delete the entity + savedEntity.setDeleted(true); + dataMetadataRepository.saveAndFlush(savedEntity); + Optional entity = + dataMetadataRepository.findByUri(testUri); + Assert.isTrue(entity.isPresent() && entity.get().isDeleted(), + "Entity should be soft-deleted"); + + // delete the entity + dataMetadataRepository.delete(savedEntity); + Assert.isTrue(!dataMetadataRepository.findByUri(testUri).isPresent(), + "Entity should be deleted"); + } +} diff --git a/metacat-metadata/src/test/java/com/netflix/metacat/metadata/store/data/repositories/DefinitionMetadataRepositoryTests.java b/metacat-metadata/src/test/java/com/netflix/metacat/metadata/store/data/repositories/DefinitionMetadataRepositoryTests.java new file mode 100644 index 000000000..a4ec128f0 --- /dev/null +++ b/metacat-metadata/src/test/java/com/netflix/metacat/metadata/store/data/repositories/DefinitionMetadataRepositoryTests.java @@ -0,0 +1,56 @@ +//CHECKSTYLE:OFF +package com.netflix.metacat.metadata.store.data.repositories; + +import com.netflix.metacat.common.QualifiedName; +import com.netflix.metacat.metadata.store.configs.UserMetadataStoreConfig; +import com.netflix.metacat.metadata.store.data.entities.DefinitionMetadataEntity; +import com.netflix.metacat.metadata.util.EntityTestUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureDataJpa; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; + +import java.util.Optional; + +/** + * Test definition metadata repository APIs + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = {UserMetadataStoreConfig.class}) +@ActiveProfiles(profiles = {"usermetadata-h2db"}) +@Transactional +@AutoConfigureDataJpa +public class DefinitionMetadataRepositoryTests { + + @Autowired + private DefinitionMetadataRepository definitionMetadataRepository; + + @Test + public void testCreateAndGet() { + QualifiedName testQName = QualifiedName.fromString("prodhive/foo/bar"); + DefinitionMetadataEntity metadataEntity = + definitionMetadataRepository.save(EntityTestUtil.createDefinitionMetadataEntity(testQName)); + + // get the entity back + DefinitionMetadataEntity savedEntity = definitionMetadataRepository.getOne(metadataEntity.getId()); + Assert.isTrue(savedEntity.equals(metadataEntity), "Retrieved entity should be the same"); + + // soft delete the entity + savedEntity.setDeleted(true); + definitionMetadataRepository.saveAndFlush(savedEntity); + Optional entity = + definitionMetadataRepository.findByName(testQName); + Assert.isTrue(entity.isPresent() && entity.get().isDeleted(), + "Entity should be soft-deleted"); + + // delete the entity + definitionMetadataRepository.delete(savedEntity); + Assert.isTrue(!definitionMetadataRepository.findByName(testQName).isPresent(), + "Entity should be deleted"); + } +} diff --git a/metacat-metadata/src/test/java/com/netflix/metacat/metadata/store/data/repositories/package-info.java b/metacat-metadata/src/test/java/com/netflix/metacat/metadata/store/data/repositories/package-info.java new file mode 100644 index 000000000..bf37165e1 --- /dev/null +++ b/metacat-metadata/src/test/java/com/netflix/metacat/metadata/store/data/repositories/package-info.java @@ -0,0 +1,22 @@ +/* + * + * Copyright 2021 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * Metacat metadata repository test classes. + */ +package com.netflix.metacat.metadata.store.data.repositories; diff --git a/metacat-metadata/src/test/java/com/netflix/metacat/metadata/util/EntityTestUtil.java b/metacat-metadata/src/test/java/com/netflix/metacat/metadata/util/EntityTestUtil.java new file mode 100644 index 000000000..4b68edcb6 --- /dev/null +++ b/metacat-metadata/src/test/java/com/netflix/metacat/metadata/util/EntityTestUtil.java @@ -0,0 +1,57 @@ +//CHECKSTYLE:OFF +package com.netflix.metacat.metadata.util; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.netflix.metacat.common.QualifiedName; +import com.netflix.metacat.metadata.store.data.entities.AuditEntity; +import com.netflix.metacat.metadata.store.data.entities.DataMetadataEntity; +import com.netflix.metacat.metadata.store.data.entities.DefinitionMetadataEntity; + +import java.time.Instant; + +public class EntityTestUtil { + public static ObjectMapper objectMapper = new ObjectMapper() + .findAndRegisterModules() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .setSerializationInclusion(JsonInclude.Include.ALWAYS); + + public static DataMetadataEntity createDataMetadataEntity() { + return createDataMetadataEntity("s3://iceberg/bucket"); + } + + public static DataMetadataEntity createDataMetadataEntity(String uri) { + return DataMetadataEntity.builder() + .uri(uri) + .data(createTestObjectNode()) + .audit(createAuditEntity()) + .build(); + } + + public static DefinitionMetadataEntity createDefinitionMetadataEntity() { + return createDefinitionMetadataEntity(QualifiedName.fromString("prodhive/foo/bar")); + } + + public static DefinitionMetadataEntity createDefinitionMetadataEntity(QualifiedName name) { + return DefinitionMetadataEntity.builder() + .name(name) + .data(createTestObjectNode()) + .audit(createAuditEntity()) + .build(); + } + + public static AuditEntity createAuditEntity() { + return AuditEntity.builder() + .createdBy("metacat_user") + .lastModifiedBy("metacat_user") + .createdDate(Instant.now()) + .lastModifiedDate(Instant.now()) + .build(); + } + + public static ObjectNode createTestObjectNode() { + return objectMapper.createObjectNode().put("size", "50"); + } +} diff --git a/metacat-metadata/src/test/java/com/netflix/metacat/metadata/util/package-info.java b/metacat-metadata/src/test/java/com/netflix/metacat/metadata/util/package-info.java new file mode 100644 index 000000000..dc39075e4 --- /dev/null +++ b/metacat-metadata/src/test/java/com/netflix/metacat/metadata/util/package-info.java @@ -0,0 +1,23 @@ +/* + * + * Copyright 2021 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * Metacat metadata utility classes. + */ +package com.netflix.metacat.metadata.util; + diff --git a/metacat-metadata/src/test/resources/application-usermetadata-h2db.yml b/metacat-metadata/src/test/resources/application-usermetadata-h2db.yml new file mode 100644 index 000000000..e4e289369 --- /dev/null +++ b/metacat-metadata/src/test/resources/application-usermetadata-h2db.yml @@ -0,0 +1,24 @@ +spring: + datasource: + platform: h2db + url: jdbc:h2:mem:testdb + username: sa + password: + schema: classpath:/h2db/schema.sql + initialization-mode: always + driverClassName: org.h2.Driver + hikari: + connection-timeout: 5000 # 5 seconds + max-lifetime: 580000 # 9 minutes 40 seconds + data-source-properties: + loginTimeout: 5 # 5 seconds + socketTimeout: 30 # 30 seconds + ApplicationName: polaris + + jpa: + hibernate: + ddl-auto: none + properties: + hibernate: + dialect: org.hibernate.dialect.H2Dialect + show_sql: true diff --git a/metacat-metadata/src/test/resources/h2db/schema.sql b/metacat-metadata/src/test/resources/h2db/schema.sql new file mode 100644 index 000000000..d9d58f8ae --- /dev/null +++ b/metacat-metadata/src/test/resources/h2db/schema.sql @@ -0,0 +1,31 @@ +drop table definition_metadata if exists; +drop table data_metadata if exists; + +create table definition_metadata ( + id IDENTITY not null primary key, + name varchar(1024) not null, + data json, + version bigint not null, + is_deleted bit default false not null, + created_by varchar(255) not null, + created_date timestamp(3) not null, + last_updated_by varchar(255) not null, + last_updated_date timestamp(3) not null, + primary key (id), + constraint def_metadata_uq unique (name) +); + +create table data_metadata ( + id IDENTITY not null primary key, + uri varchar(4000) not null, + data json, + version bigint not null, + is_deleted bit default false not null, + created_by varchar(255) not null, + created_date timestamp(3) not null, + last_updated_by varchar(255) not null, + last_updated_date timestamp(3) not null, + primary key (id), + constraint data_uq unique (uri) +); +