mirror of https://github.com/halo-dev/halo
Create unstructured Extension which can store any Extensions (#2111)
* Extract ExtensionOperator and MetadataOperator * Move groupVersionKind methods up to ExtensionOperator interface * Add Unstructured Extension for generic Extension * Refine mapping of GVK and Scheme * Add two compatible methodspull/2119/head
parent
0f4ae08fd8
commit
b5d7f194ef
|
@ -1,6 +1,5 @@
|
||||||
package run.halo.app.extension;
|
package run.halo.app.extension;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,33 +10,10 @@ import lombok.Data;
|
||||||
@Data
|
@Data
|
||||||
public abstract class AbstractExtension implements Extension {
|
public abstract class AbstractExtension implements Extension {
|
||||||
|
|
||||||
@Schema(required = true)
|
|
||||||
private String apiVersion;
|
private String apiVersion;
|
||||||
|
|
||||||
@Schema(required = true)
|
|
||||||
private String kind;
|
private String kind;
|
||||||
|
|
||||||
@Schema(required = true)
|
private MetadataOperator metadata;
|
||||||
private Metadata metadata;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void groupVersionKind(GroupVersionKind gvk) {
|
|
||||||
this.apiVersion = gvk.groupVersion().toString();
|
|
||||||
this.kind = gvk.kind();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public GroupVersionKind groupVersionKind() {
|
|
||||||
return GroupVersionKind.fromAPIVersionAndKind(this.apiVersion, this.kind);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void metadata(Metadata metadata) {
|
|
||||||
this.metadata = metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Metadata metadata() {
|
|
||||||
return this.metadata;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ public class DefaultExtensionClient implements ExtensionClient {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <E extends Extension> void create(E extension) {
|
public <E extends Extension> void create(E extension) {
|
||||||
extension.metadata().setCreationTimestamp(Instant.now());
|
extension.getMetadata().setCreationTimestamp(Instant.now());
|
||||||
var extensionStore = converter.convertTo(extension);
|
var extensionStore = converter.convertTo(extension);
|
||||||
storeClient.create(extensionStore.getName(), extensionStore.getData());
|
storeClient.create(extensionStore.getName(), extensionStore.getData());
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ public class DefaultExtensionClient implements ExtensionClient {
|
||||||
@Override
|
@Override
|
||||||
public <E extends Extension> void update(E extension) {
|
public <E extends Extension> void update(E extension) {
|
||||||
var extensionStore = converter.convertTo(extension);
|
var extensionStore = converter.convertTo(extension);
|
||||||
Assert.notNull(extension.metadata().getVersion(),
|
Assert.notNull(extension.getMetadata().getVersion(),
|
||||||
"Extension version must not be null when updating");
|
"Extension version must not be null when updating");
|
||||||
storeClient.update(extensionStore.getName(), extensionStore.getVersion(),
|
storeClient.update(extensionStore.getName(), extensionStore.getVersion(),
|
||||||
extensionStore.getData());
|
extensionStore.getData());
|
||||||
|
|
|
@ -4,34 +4,6 @@ package run.halo.app.extension;
|
||||||
* Extension is an interface which represents an Extension. It contains setters and getters of
|
* Extension is an interface which represents an Extension. It contains setters and getters of
|
||||||
* GroupVersionKind and Metadata.
|
* GroupVersionKind and Metadata.
|
||||||
*/
|
*/
|
||||||
public interface Extension {
|
public interface Extension extends ExtensionOperator {
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets GroupVersionKind of the Extension.
|
|
||||||
*
|
|
||||||
* @param gvk is GroupVersionKind data.
|
|
||||||
*/
|
|
||||||
void groupVersionKind(GroupVersionKind gvk);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets GroupVersionKind of the Extension.
|
|
||||||
*
|
|
||||||
* @return GroupVersionKind of the Extension.
|
|
||||||
*/
|
|
||||||
GroupVersionKind groupVersionKind();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets metadata of the Extension.
|
|
||||||
*
|
|
||||||
* @param metadata metadata of the Extension.
|
|
||||||
*/
|
|
||||||
void metadata(Metadata metadata);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets metadata of the Extension.
|
|
||||||
*
|
|
||||||
* @return metadata of the Extension.
|
|
||||||
*/
|
|
||||||
Metadata metadata();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
package run.halo.app.extension;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ExtensionOperator contains some getters and setters for required fields of Extension.
|
||||||
|
*
|
||||||
|
* @author johnniang
|
||||||
|
*/
|
||||||
|
public interface ExtensionOperator {
|
||||||
|
|
||||||
|
@Schema(required = true)
|
||||||
|
@JsonProperty("apiVersion")
|
||||||
|
String getApiVersion();
|
||||||
|
|
||||||
|
@Schema(required = true)
|
||||||
|
@JsonProperty("kind")
|
||||||
|
String getKind();
|
||||||
|
|
||||||
|
@Schema(required = true, implementation = Metadata.class)
|
||||||
|
@JsonProperty("metadata")
|
||||||
|
MetadataOperator getMetadata();
|
||||||
|
|
||||||
|
void setApiVersion(String apiVersion);
|
||||||
|
|
||||||
|
void setKind(String kind);
|
||||||
|
|
||||||
|
void setMetadata(MetadataOperator metadata);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is only for backward compatibility. Same as {@link #getMetadata()}.
|
||||||
|
*
|
||||||
|
* @return Extension metadata.
|
||||||
|
* @see #getMetadata()
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
@Deprecated(forRemoval = true)
|
||||||
|
default MetadataOperator metadata() {
|
||||||
|
return getMetadata();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is only for backward compatibility. Same as
|
||||||
|
* {@link #setMetadata(MetadataOperator)}.
|
||||||
|
*
|
||||||
|
* @param metadata is Extension metadata.
|
||||||
|
* @see #setMetadata(MetadataOperator)
|
||||||
|
*/
|
||||||
|
@Deprecated(forRemoval = true)
|
||||||
|
default void metadata(MetadataOperator metadata) {
|
||||||
|
setMetadata(metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets GroupVersionKind of the Extension.
|
||||||
|
*
|
||||||
|
* @param gvk is GroupVersionKind data.
|
||||||
|
*/
|
||||||
|
default void groupVersionKind(GroupVersionKind gvk) {
|
||||||
|
setApiVersion(gvk.groupVersion().toString());
|
||||||
|
setKind(gvk.kind());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets GroupVersionKind of the Extension.
|
||||||
|
*
|
||||||
|
* @return GroupVersionKind of the Extension.
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
default GroupVersionKind groupVersionKind() {
|
||||||
|
return GroupVersionKind.fromAPIVersionAndKind(getApiVersion(), getKind());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -33,8 +33,9 @@ public class JSONExtensionConverter implements ExtensionConverter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <E extends Extension> ExtensionStore convertTo(E extension) {
|
public <E extends Extension> ExtensionStore convertTo(E extension) {
|
||||||
var scheme = Schemes.INSTANCE.get(extension.getClass());
|
var gvk = extension.groupVersionKind();
|
||||||
var storeName = ExtensionUtil.buildStoreName(scheme, extension.metadata().getName());
|
var scheme = Schemes.INSTANCE.get(gvk);
|
||||||
|
var storeName = ExtensionUtil.buildStoreName(scheme, extension.getMetadata().getName());
|
||||||
try {
|
try {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("JSON schema({}): {}", scheme.type(),
|
logger.debug("JSON schema({}): {}", scheme.type(),
|
||||||
|
@ -56,7 +57,7 @@ public class JSONExtensionConverter implements ExtensionConverter {
|
||||||
|
|
||||||
// keep converting
|
// keep converting
|
||||||
var data = objectMapper.writeValueAsBytes(extensionNode);
|
var data = objectMapper.writeValueAsBytes(extensionNode);
|
||||||
var version = extension.metadata().getVersion();
|
var version = extension.getMetadata().getVersion();
|
||||||
return new ExtensionStore(storeName, data, version);
|
return new ExtensionStore(storeName, data, version);
|
||||||
} catch (JsonProcessingException e) {
|
} catch (JsonProcessingException e) {
|
||||||
throw new ExtensionConvertException("Failed write Extension as bytes", e);
|
throw new ExtensionConvertException("Failed write Extension as bytes", e);
|
||||||
|
@ -67,7 +68,7 @@ public class JSONExtensionConverter implements ExtensionConverter {
|
||||||
public <E extends Extension> E convertFrom(Class<E> type, ExtensionStore extensionStore) {
|
public <E extends Extension> E convertFrom(Class<E> type, ExtensionStore extensionStore) {
|
||||||
try {
|
try {
|
||||||
var extension = objectMapper.readValue(extensionStore.getData(), type);
|
var extension = objectMapper.readValue(extensionStore.getData(), type);
|
||||||
extension.metadata().setVersion(extensionStore.getVersion());
|
extension.getMetadata().setVersion(extensionStore.getVersion());
|
||||||
return extension;
|
return extension;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new ExtensionConvertException("Failed to read Extension " + type + " from bytes",
|
throw new ExtensionConvertException("Failed to read Extension " + type + " from bytes",
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package run.halo.app.extension;
|
package run.halo.app.extension;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
@ -11,42 +10,36 @@ import lombok.Data;
|
||||||
* @author johnniang
|
* @author johnniang
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class Metadata {
|
public class Metadata implements MetadataOperator {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metadata name. The name is unique globally.
|
* Metadata name. The name is unique globally.
|
||||||
*/
|
*/
|
||||||
@Schema(required = true)
|
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Labels are like key-value format.
|
* Labels are like key-value format.
|
||||||
*/
|
*/
|
||||||
@Schema(nullable = true)
|
|
||||||
private Map<String, String> labels;
|
private Map<String, String> labels;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotations are like key-value format.
|
* Annotations are like key-value format.
|
||||||
*/
|
*/
|
||||||
@Schema(nullable = true)
|
|
||||||
private Map<String, String> annotations;
|
private Map<String, String> annotations;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current version of the Extension. It will be bumped up every update.
|
* Current version of the Extension. It will be bumped up every update.
|
||||||
*/
|
*/
|
||||||
@Schema(nullable = true)
|
|
||||||
private Long version;
|
private Long version;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creation timestamp of the Extension.
|
* Creation timestamp of the Extension.
|
||||||
*/
|
*/
|
||||||
@Schema(nullable = true)
|
|
||||||
private Instant creationTimestamp;
|
private Instant creationTimestamp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletion timestamp of the Extension.
|
* Deletion timestamp of the Extension.
|
||||||
*/
|
*/
|
||||||
@Schema(nullable = true)
|
|
||||||
private Instant deletionTimestamp;
|
private Instant deletionTimestamp;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
package run.halo.app.extension;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MetadataOperator contains some getters and setters for required fields of metadata.
|
||||||
|
*
|
||||||
|
* @author johnniang
|
||||||
|
*/
|
||||||
|
@JsonDeserialize(as = Metadata.class)
|
||||||
|
@Schema(implementation = Metadata.class)
|
||||||
|
public interface MetadataOperator {
|
||||||
|
|
||||||
|
@Schema(required = true)
|
||||||
|
@JsonProperty("name")
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
@Schema(nullable = true)
|
||||||
|
@JsonProperty("labels")
|
||||||
|
Map<String, String> getLabels();
|
||||||
|
|
||||||
|
@Schema(nullable = true)
|
||||||
|
@JsonProperty("annotations")
|
||||||
|
Map<String, String> getAnnotations();
|
||||||
|
|
||||||
|
@Schema(nullable = true)
|
||||||
|
@JsonProperty("version")
|
||||||
|
Long getVersion();
|
||||||
|
|
||||||
|
@Schema(nullable = true)
|
||||||
|
@JsonProperty("creationTimestamp")
|
||||||
|
Instant getCreationTimestamp();
|
||||||
|
|
||||||
|
@Schema(nullable = true)
|
||||||
|
@JsonProperty("deletionTimestamp")
|
||||||
|
Instant getDeletionTimestamp();
|
||||||
|
|
||||||
|
void setName(String name);
|
||||||
|
|
||||||
|
void setLabels(Map<String, String> labels);
|
||||||
|
|
||||||
|
void setAnnotations(Map<String, String> annotations);
|
||||||
|
|
||||||
|
void setVersion(Long version);
|
||||||
|
|
||||||
|
void setCreationTimestamp(Instant creationTimestamp);
|
||||||
|
|
||||||
|
void setDeletionTimestamp(Instant deletionTimestamp);
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package run.halo.app.extension;
|
package run.halo.app.extension;
|
||||||
|
|
||||||
|
import com.github.victools.jsonschema.generator.Option;
|
||||||
import com.github.victools.jsonschema.generator.OptionPreset;
|
import com.github.victools.jsonschema.generator.OptionPreset;
|
||||||
import com.github.victools.jsonschema.generator.SchemaGenerator;
|
import com.github.victools.jsonschema.generator.SchemaGenerator;
|
||||||
import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder;
|
import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder;
|
||||||
|
@ -39,23 +40,22 @@ public enum Schemes {
|
||||||
/**
|
/**
|
||||||
* The map mapping GroupVersionKind and type of Extension.
|
* The map mapping GroupVersionKind and type of Extension.
|
||||||
*/
|
*/
|
||||||
private final Map<GroupVersionKind, Class<? extends Extension>> gvkToType;
|
private final Map<GroupVersionKind, Scheme> gvkToScheme;
|
||||||
|
|
||||||
Schemes() {
|
Schemes() {
|
||||||
schemes = new HashSet<>();
|
schemes = new HashSet<>();
|
||||||
typeToScheme = new HashMap<>();
|
typeToScheme = new HashMap<>();
|
||||||
gvkToType = new HashMap<>();
|
gvkToScheme = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear registered schemes.
|
* Clear registered schemes.
|
||||||
* <p>
|
|
||||||
* This method is only for test.
|
* This method is only for test.
|
||||||
*/
|
*/
|
||||||
void clear() {
|
void clear() {
|
||||||
schemes.clear();
|
schemes.clear();
|
||||||
typeToScheme.clear();
|
typeToScheme.clear();
|
||||||
gvkToType.clear();
|
gvkToScheme.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -74,10 +74,17 @@ public enum Schemes {
|
||||||
type.getName()));
|
type.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Move the generation logic outside.
|
||||||
// generate JSON schema
|
// generate JSON schema
|
||||||
var module = new Swagger2Module();
|
var module = new Swagger2Module();
|
||||||
var config =
|
var config =
|
||||||
new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2019_09, OptionPreset.PLAIN_JSON)
|
new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2019_09, OptionPreset.PLAIN_JSON)
|
||||||
|
.with(
|
||||||
|
// See https://victools.github.io/jsonschema-generator/#generator-options
|
||||||
|
// fore more.
|
||||||
|
Option.INLINE_ALL_SCHEMAS,
|
||||||
|
Option.MAP_VALUES_AS_ADDITIONAL_PROPERTIES
|
||||||
|
)
|
||||||
.with(module)
|
.with(module)
|
||||||
.build();
|
.build();
|
||||||
var generator = new SchemaGenerator(config);
|
var generator = new SchemaGenerator(config);
|
||||||
|
@ -102,7 +109,7 @@ public enum Schemes {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
typeToScheme.put(scheme.type(), scheme);
|
typeToScheme.put(scheme.type(), scheme);
|
||||||
gvkToType.put(scheme.groupVersionKind(), scheme.type());
|
gvkToScheme.put(scheme.groupVersionKind(), scheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -116,6 +123,10 @@ public enum Schemes {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Optional<Scheme> fetch(GroupVersionKind gvk) {
|
||||||
|
return Optional.ofNullable(gvkToScheme.get(gvk));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a scheme using Extension type.
|
* Gets a scheme using Extension type.
|
||||||
*
|
*
|
||||||
|
@ -128,4 +139,9 @@ public enum Schemes {
|
||||||
"Scheme was not found for Extension " + type.getSimpleName()));
|
"Scheme was not found for Extension " + type.getSimpleName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Scheme get(GroupVersionKind gvk) {
|
||||||
|
return fetch(gvk).orElseThrow(() -> new SchemeNotFoundException(
|
||||||
|
"Scheme was not found for GVK " + gvk));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
package run.halo.app.extension;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unstructured is a generic Extension, which wraps ObjectNode to maintain the Extension data, like
|
||||||
|
* apiVersion, kind, metadata and others.
|
||||||
|
*
|
||||||
|
* @author johnniang
|
||||||
|
*/
|
||||||
|
@JsonSerialize(using = Unstructured.UnstructuredSerializer.class)
|
||||||
|
@JsonDeserialize(using = Unstructured.UnstructuredDeserializer.class)
|
||||||
|
public class Unstructured implements Extension {
|
||||||
|
|
||||||
|
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
|
static {
|
||||||
|
OBJECT_MAPPER.registerModule(new JavaTimeModule());
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ObjectNode extension;
|
||||||
|
|
||||||
|
public Unstructured() {
|
||||||
|
this(OBJECT_MAPPER.createObjectNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Unstructured(ObjectNode extension) {
|
||||||
|
this.extension = extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getApiVersion() {
|
||||||
|
return extension.get("apiVersion").asText();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getKind() {
|
||||||
|
return extension.get("kind").asText();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MetadataOperator getMetadata() {
|
||||||
|
var metaMap = extension.get("metadata");
|
||||||
|
return OBJECT_MAPPER.convertValue(metaMap, Metadata.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setApiVersion(String apiVersion) {
|
||||||
|
extension.put("apiVersion", apiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setKind(String kind) {
|
||||||
|
extension.put("kind", kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMetadata(MetadataOperator metadata) {
|
||||||
|
JsonNode metaNode = OBJECT_MAPPER.valueToTree(metadata);
|
||||||
|
extension.set("metadata", metaNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectNode getExtension() {
|
||||||
|
return extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Add other convenient methods here to set and get nested fields in the future.
|
||||||
|
|
||||||
|
public static class UnstructuredSerializer extends JsonSerializer<Unstructured> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serialize(Unstructured value, JsonGenerator gen, SerializerProvider serializers)
|
||||||
|
throws IOException {
|
||||||
|
gen.writeTree(value.extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class UnstructuredDeserializer extends JsonDeserializer<Unstructured> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Unstructured deserialize(JsonParser p, DeserializationContext ctxt)
|
||||||
|
throws IOException {
|
||||||
|
return new Unstructured(p.getCodec().readTree(p));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -45,7 +45,7 @@ class AbstractExtensionTest {
|
||||||
|
|
||||||
Metadata metadata = new Metadata();
|
Metadata metadata = new Metadata();
|
||||||
metadata.setName("fake");
|
metadata.setName("fake");
|
||||||
extension.metadata(metadata);
|
extension.setMetadata(metadata);
|
||||||
|
|
||||||
assertEquals(metadata, extension.getMetadata());
|
assertEquals(metadata, extension.getMetadata());
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
@ -58,11 +59,35 @@ class DefaultExtensionClientTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
ExtensionStore createExtensionStore(String name) {
|
ExtensionStore createExtensionStore(String name) {
|
||||||
|
return createExtensionStore(name, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtensionStore createExtensionStore(String name, Long version) {
|
||||||
var extensionStore = new ExtensionStore();
|
var extensionStore = new ExtensionStore();
|
||||||
extensionStore.setName(name);
|
extensionStore.setName(name);
|
||||||
|
extensionStore.setVersion(version);
|
||||||
return extensionStore;
|
return extensionStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Unstructured createUnstructured() throws JsonProcessingException {
|
||||||
|
String extensionJson = """
|
||||||
|
{
|
||||||
|
"apiVersion": "fake.halo.run/v1alpha1",
|
||||||
|
"kind": "Fake",
|
||||||
|
"metadata": {
|
||||||
|
"labels": {
|
||||||
|
"category": "fake",
|
||||||
|
"default": "true"
|
||||||
|
},
|
||||||
|
"name": "fake",
|
||||||
|
"creationTimestamp": "2011-12-03T10:15:30Z",
|
||||||
|
"version": 12345
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
return Unstructured.OBJECT_MAPPER.readValue(extensionJson, Unstructured.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldThrowSchemeNotFoundExceptionWhenSchemeNotRegistered() {
|
void shouldThrowSchemeNotFoundExceptionWhenSchemeNotRegistered() {
|
||||||
class UnRegisteredExtension extends AbstractExtension {
|
class UnRegisteredExtension extends AbstractExtension {
|
||||||
|
@ -205,8 +230,24 @@ class DefaultExtensionClientTest {
|
||||||
|
|
||||||
client.create(fake);
|
client.create(fake);
|
||||||
|
|
||||||
verify(converter, times(1)).convertTo(any());
|
verify(converter, times(1)).convertTo(eq(fake));
|
||||||
verify(storeClient, times(1)).create(any(), any());
|
verify(storeClient, times(1)).create(eq("/registry/fake.halo.run/fakes/fake"), any());
|
||||||
|
assertNotNull(fake.getMetadata().getCreationTimestamp());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateUsingUnstructuredSuccessfully() throws JsonProcessingException {
|
||||||
|
var fake = createUnstructured();
|
||||||
|
|
||||||
|
when(converter.convertTo(any())).thenReturn(
|
||||||
|
createExtensionStore("/registry/fake.halo.run/fakes/fake"));
|
||||||
|
when(storeClient.create(any(), any())).thenReturn(
|
||||||
|
createExtensionStore("/registry/fake.halo.run/fakes/fake"));
|
||||||
|
|
||||||
|
client.create(fake);
|
||||||
|
|
||||||
|
verify(converter, times(1)).convertTo(eq(fake));
|
||||||
|
verify(storeClient, times(1)).create(eq("/registry/fake.halo.run/fakes/fake"), any());
|
||||||
assertNotNull(fake.getMetadata().getCreationTimestamp());
|
assertNotNull(fake.getMetadata().getCreationTimestamp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,14 +255,30 @@ class DefaultExtensionClientTest {
|
||||||
void shouldUpdateSuccessfully() {
|
void shouldUpdateSuccessfully() {
|
||||||
var fake = createFakeExtension("fake", 2L);
|
var fake = createFakeExtension("fake", 2L);
|
||||||
when(converter.convertTo(any())).thenReturn(
|
when(converter.convertTo(any())).thenReturn(
|
||||||
createExtensionStore("/registry/fake.halo.run/fakes/fake"));
|
createExtensionStore("/registry/fake.halo.run/fakes/fake", 2L));
|
||||||
when(storeClient.update(any(), any(), any())).thenReturn(
|
when(storeClient.update(any(), any(), any())).thenReturn(
|
||||||
createExtensionStore("/registry/fake.halo.run/fakes/fake"));
|
createExtensionStore("/registry/fake.halo.run/fakes/fake", 2L));
|
||||||
|
|
||||||
client.update(fake);
|
client.update(fake);
|
||||||
|
|
||||||
verify(converter, times(1)).convertTo(any());
|
verify(converter, times(1)).convertTo(eq(fake));
|
||||||
verify(storeClient, times(1)).update(any(), any(), any());
|
verify(storeClient, times(1))
|
||||||
|
.update(eq("/registry/fake.halo.run/fakes/fake"), eq(2L), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUpdateUnstructuredSuccessfully() throws JsonProcessingException {
|
||||||
|
var fake = createUnstructured();
|
||||||
|
when(converter.convertTo(any())).thenReturn(
|
||||||
|
createExtensionStore("/registry/fake.halo.run/fakes/fake", 12345L));
|
||||||
|
when(storeClient.update(any(), any(), any())).thenReturn(
|
||||||
|
createExtensionStore("/registry/fake.halo.run/fakes/fake", 12345L));
|
||||||
|
|
||||||
|
client.update(fake);
|
||||||
|
|
||||||
|
verify(converter, times(1)).convertTo(eq(fake));
|
||||||
|
verify(storeClient, times(1))
|
||||||
|
.update(eq("/registry/fake.halo.run/fakes/fake"), eq(12345L), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -87,7 +87,7 @@ class JSONExtensionConverterTest {
|
||||||
Metadata metadata = new Metadata();
|
Metadata metadata = new Metadata();
|
||||||
metadata.setName(name);
|
metadata.setName(name);
|
||||||
metadata.setVersion(version);
|
metadata.setVersion(version);
|
||||||
fake.metadata(metadata);
|
fake.setMetadata(metadata);
|
||||||
|
|
||||||
return fake;
|
return fake;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,13 @@ package run.halo.app.extension;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static run.halo.app.extension.GroupVersionKind.fromAPIVersionAndKind;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import run.halo.app.extension.exception.ExtensionException;
|
import run.halo.app.extension.exception.ExtensionException;
|
||||||
|
import run.halo.app.extension.exception.SchemeNotFoundException;
|
||||||
|
|
||||||
class SchemesTest {
|
class SchemesTest {
|
||||||
|
|
||||||
|
@ -35,6 +37,13 @@ class SchemesTest {
|
||||||
void shouldFetchNothingWhenUnregistered() {
|
void shouldFetchNothingWhenUnregistered() {
|
||||||
var scheme = Schemes.INSTANCE.fetch(FakeExtension.class);
|
var scheme = Schemes.INSTANCE.fetch(FakeExtension.class);
|
||||||
assertEquals(Optional.empty(), scheme);
|
assertEquals(Optional.empty(), scheme);
|
||||||
|
assertThrows(SchemeNotFoundException.class,
|
||||||
|
() -> Schemes.INSTANCE.get(FakeExtension.class));
|
||||||
|
|
||||||
|
var gvk = fromAPIVersionAndKind("fake.halo.run/v1alpha1", "Fake");
|
||||||
|
scheme = Schemes.INSTANCE.fetch(gvk);
|
||||||
|
assertEquals(Optional.empty(), scheme);
|
||||||
|
assertThrows(SchemeNotFoundException.class, () -> Schemes.INSTANCE.get(gvk));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -43,5 +52,8 @@ class SchemesTest {
|
||||||
|
|
||||||
var scheme = Schemes.INSTANCE.fetch(FakeExtension.class);
|
var scheme = Schemes.INSTANCE.fetch(FakeExtension.class);
|
||||||
assertTrue(scheme.isPresent());
|
assertTrue(scheme.isPresent());
|
||||||
|
|
||||||
|
scheme = Schemes.INSTANCE.fetch(fromAPIVersionAndKind("fake.halo.run/v1alpha1", "Fake"));
|
||||||
|
assertTrue(scheme.isPresent());
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
package run.halo.app.extension;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class UnstructuredTest {
|
||||||
|
|
||||||
|
ObjectMapper objectMapper = Unstructured.OBJECT_MAPPER;
|
||||||
|
|
||||||
|
String extensionJson = """
|
||||||
|
{
|
||||||
|
"apiVersion": "fake.halo.run/v1alpha1",
|
||||||
|
"kind": "Fake",
|
||||||
|
"metadata": {
|
||||||
|
"labels": {
|
||||||
|
"category": "fake",
|
||||||
|
"default": "true"
|
||||||
|
},
|
||||||
|
"name": "fake-extension",
|
||||||
|
"creationTimestamp": "2011-12-03T10:15:30Z",
|
||||||
|
"version": 12345
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setUpGlobally() {
|
||||||
|
Schemes.INSTANCE.register(FakeExtension.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSerializeCorrectly() throws JsonProcessingException {
|
||||||
|
var extensionNode = (ObjectNode) objectMapper.readTree(extensionJson);
|
||||||
|
var extension = new Unstructured(extensionNode);
|
||||||
|
|
||||||
|
var gotNode = objectMapper.valueToTree(extension);
|
||||||
|
assertEquals(extensionNode, gotNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldDeserializeCorrectly() throws JsonProcessingException {
|
||||||
|
var extension = objectMapper.readValue(extensionJson, Unstructured.class);
|
||||||
|
var wantJsonNode = objectMapper.readTree(extensionJson);
|
||||||
|
assertEquals(wantJsonNode, extension.getExtension());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldGetExtensionCorrectly() throws JsonProcessingException {
|
||||||
|
var extension = objectMapper.readValue(extensionJson, Unstructured.class);
|
||||||
|
|
||||||
|
assertEquals("fake.halo.run/v1alpha1", extension.getApiVersion());
|
||||||
|
assertEquals("Fake", extension.getKind());
|
||||||
|
assertEquals(createMetadata(), extension.getMetadata());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldSetExtensionCorrectly() {
|
||||||
|
var extension = new Unstructured();
|
||||||
|
extension.setApiVersion("fake.halo.run/v1alpha1");
|
||||||
|
extension.setKind("Fake");
|
||||||
|
extension.setMetadata(createMetadata());
|
||||||
|
|
||||||
|
assertEquals("fake.halo.run/v1alpha1", extension.getApiVersion());
|
||||||
|
assertEquals("Fake", extension.getKind());
|
||||||
|
assertEquals(createMetadata(), extension.getMetadata());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Metadata createMetadata() {
|
||||||
|
var metadata = new Metadata();
|
||||||
|
metadata.setName("fake-extension");
|
||||||
|
metadata.setLabels(Map.of("category", "fake", "default", "true"));
|
||||||
|
metadata.setCreationTimestamp(Instant.parse("2011-12-03T10:15:30Z"));
|
||||||
|
metadata.setVersion(12345L);
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue