Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate API Metadata Creation to 🧅 #6839

Merged
merged 7 commits into from Mar 22, 2024
Expand Up @@ -17,8 +17,8 @@

import io.gravitee.apim.core.DomainService;
import io.gravitee.apim.core.api.domain_service.ApiMetadataDecoderDomainService.ApiMetadataDecodeContext;
import io.gravitee.apim.core.api.domain_service.ApiMetadataDecoderDomainService.PrimaryOwnerMetadataDecodeContext;
import io.gravitee.apim.core.api.model.Api;
import io.gravitee.apim.core.documentation.model.PrimaryOwnerApiTemplateData;
import io.gravitee.apim.core.membership.model.PrimaryOwnerEntity;
import io.gravitee.apim.core.search.Indexer;
import io.gravitee.apim.core.search.model.IndexableApi;
Expand All @@ -45,7 +45,7 @@ public void index(Indexer.IndexationContext context, Api apiToIndex, PrimaryOwne
.createdAt(Date.from(apiToIndex.getCreatedAt().toInstant()))
.updatedAt(Date.from(apiToIndex.getUpdatedAt().toInstant()))
.primaryOwner(
new PrimaryOwnerMetadataDecodeContext(
new PrimaryOwnerApiTemplateData(
primaryOwner.id(),
primaryOwner.displayName(),
primaryOwner.email(),
Expand Down
Expand Up @@ -16,18 +16,18 @@
package io.gravitee.apim.core.api.domain_service;

import io.gravitee.apim.core.DomainService;
import io.gravitee.apim.core.api.model.Api;
import io.gravitee.apim.core.api.query_service.ApiMetadataQueryService;
import io.gravitee.apim.core.documentation.model.ApiFreemarkerTemplate;
import io.gravitee.apim.core.exception.TechnicalDomainException;
import io.gravitee.apim.core.membership.model.PrimaryOwnerEntity;
import io.gravitee.apim.core.template.TemplateProcessor;
import io.gravitee.apim.core.template.TemplateProcessorException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.SuperBuilder;
import lombok.extern.slf4j.Slf4j;

@Slf4j
Expand All @@ -54,6 +54,15 @@ public Map<String, String> decodeMetadata(String apiId, ApiMetadataDecodeContext
return decode(metadata, context);
}

public String decodeMetadataValue(String value, ApiMetadataDecodeContext context) {
try {
return templateProcessor.processInlineTemplate(value, Collections.singletonMap("api", context));
} catch (TemplateProcessorException e) {
log.warn("Error while validating template '{}' from reader:\n{}", e.getTemplate(), e.getMessage());
return value;
}
}

private Map<String, String> decode(Map<String, String> metadata, ApiMetadataDecodeContext context) {
if (metadata.isEmpty()) {
return metadata;
Expand All @@ -74,26 +83,11 @@ private Map<String, String> decode(Map<String, String> metadata, ApiMetadataDeco
}
}

@Builder
@AllArgsConstructor
@Data
public static class ApiMetadataDecodeContext {
@SuperBuilder
public static class ApiMetadataDecodeContext extends ApiFreemarkerTemplate {

private String name;
private String description;
private Date createdAt;
private Date updatedAt;
private PrimaryOwnerMetadataDecodeContext primaryOwner;
}

@Builder
@AllArgsConstructor
@Data
public static class PrimaryOwnerMetadataDecodeContext {

private String id;
private String displayName;
private String email;
private String type;
public ApiMetadataDecodeContext(Api api, Map<String, String> metadata, PrimaryOwnerEntity primaryOwner) {
super(api, metadata, primaryOwner);
}
}
}
Expand Up @@ -17,6 +17,7 @@

import io.gravitee.apim.core.DomainService;
import io.gravitee.apim.core.api.model.ApiMetadata;
import io.gravitee.apim.core.api.model.NewApiMetadata;
import io.gravitee.apim.core.audit.domain_service.AuditDomainService;
import io.gravitee.apim.core.audit.model.ApiAuditLogEntity;
import io.gravitee.apim.core.audit.model.AuditInfo;
Expand All @@ -25,6 +26,7 @@
import io.gravitee.apim.core.exception.TechnicalDomainException;
import io.gravitee.apim.core.metadata.crud_service.MetadataCrudService;
import io.gravitee.apim.core.metadata.model.Metadata;
import io.gravitee.apim.core.metadata.model.MetadataId;
import io.gravitee.common.utils.IdGenerator;
import io.gravitee.common.utils.TimeProvider;
import java.util.List;
Expand Down Expand Up @@ -77,6 +79,46 @@ public void saveApiMetadata(String apiId, List<ApiMetadata> metadata, AuditInfo
throw new TechnicalDomainException("Not yet implemented");
}

public ApiMetadata create(NewApiMetadata newApiMetadata, AuditInfo auditInfo) {
var now = TimeProvider.now();
var createdMetadata = metadataCrudService.create(
Metadata
.builder()
.key(newApiMetadata.getKey())
.format(newApiMetadata.getFormat())
.name(newApiMetadata.getName())
.value(newApiMetadata.getValue())
.referenceType(Metadata.ReferenceType.API)
.referenceId(newApiMetadata.getApiId())
.createdAt(now)
.updatedAt(now)
.build()
);
createAuditLog(createdMetadata, auditInfo);

var defaultValue =
this.metadataCrudService.findById(
MetadataId
.builder()
.key(createdMetadata.getKey())
.referenceId("_")
.referenceType(Metadata.ReferenceType.DEFAULT)
.build()
)
.map(Metadata::getValue)
.orElse(null);

return ApiMetadata
.builder()
.key(createdMetadata.getKey())
.format(createdMetadata.getFormat())
.name(createdMetadata.getName())
.value(createdMetadata.getValue())
.defaultValue(defaultValue)
.apiId(createdMetadata.getReferenceId())
.build();
}

private void createAuditLog(Metadata created, AuditInfo auditInfo) {
auditService.createApiAuditLog(
ApiAuditLogEntity
Expand Down
@@ -0,0 +1,99 @@
/*
* Copyright © 2015 The Gravitee team (http://gravitee.io)
*
* 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.
*/
package io.gravitee.apim.core.api.domain_service;

import io.gravitee.apim.core.DomainService;
import io.gravitee.apim.core.api.exception.DuplicateApiMetadataKeyException;
import io.gravitee.apim.core.api.exception.DuplicateApiMetadataNameException;
import io.gravitee.apim.core.api.exception.InvalidApiMetadataValueException;
import io.gravitee.apim.core.api.model.Api;
import io.gravitee.apim.core.api.query_service.ApiMetadataQueryService;
import io.gravitee.apim.core.membership.domain_service.ApiPrimaryOwnerDomainService;
import io.gravitee.apim.core.metadata.crud_service.MetadataCrudService;
import io.gravitee.apim.core.metadata.model.Metadata;
import io.gravitee.apim.core.metadata.model.MetadataId;
import jakarta.mail.internet.InternetAddress;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Map;
import java.util.Objects;
import lombok.AllArgsConstructor;

@AllArgsConstructor
@DomainService
public class ValidateApiMetadataDomainService {

private final ApiMetadataQueryService metadataQueryService;
private final MetadataCrudService metadataCrudService;
private final ApiPrimaryOwnerDomainService apiPrimaryOwnerDomainService;
private final ApiMetadataDecoderDomainService apiMetadataDecoderDomainService;

private final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");

public void validateUniqueKey(String apiId, String key) {
this.metadataCrudService.findById(
MetadataId.builder().key(key).referenceId(apiId).referenceType(Metadata.ReferenceType.API).build()
)
.ifPresent(m -> {
throw new DuplicateApiMetadataKeyException(apiId, key);
});
}

public void validateUniqueName(String apiId, String name) {
this.metadataQueryService.findApiMetadata(apiId)
.values()
.forEach(val -> {
if (val.getName().equalsIgnoreCase(name)) {
throw new DuplicateApiMetadataNameException(apiId, name);
}
});
}

public void validateValueByFormat(Api api, String organizationId, String value, Metadata.MetadataFormat format) {
var valueToCheck = Objects.nonNull(value) && value.startsWith("${") ? this.getDecodedValue(api, organizationId, value) : value;

try {
switch (format) {
case URL:
new URL(valueToCheck);
break;
case MAIL:
final InternetAddress email = new InternetAddress(valueToCheck);
email.validate();
break;
case DATE:
SIMPLE_DATE_FORMAT.setLenient(false);
SIMPLE_DATE_FORMAT.parse(valueToCheck);
break;
case NUMERIC:
Double.valueOf(valueToCheck);
break;
}
} catch (Exception e) {
throw new InvalidApiMetadataValueException(value, format.name());
}
}

private String getDecodedValue(Api api, String organizationId, String value) {
var apiTemplate = new ApiMetadataDecoderDomainService.ApiMetadataDecodeContext(
api,
Map.of(), // Metadata cannot reference another metadata entry
apiPrimaryOwnerDomainService.getApiPrimaryOwner(organizationId, api.getId())
);

return this.apiMetadataDecoderDomainService.decodeMetadataValue(value, apiTemplate);
}
}
@@ -0,0 +1,25 @@
/*
* Copyright © 2015 The Gravitee team (http://gravitee.io)
*
* 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.
*/
package io.gravitee.apim.core.api.exception;

import io.gravitee.apim.core.exception.ValidationDomainException;

public class DuplicateApiMetadataKeyException extends ValidationDomainException {

public DuplicateApiMetadataKeyException(String apiId, String key) {
super("Key [" + key + "] already exists for API [" + apiId + "]");
}
}
@@ -0,0 +1,25 @@
/*
* Copyright © 2015 The Gravitee team (http://gravitee.io)
*
* 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.
*/
package io.gravitee.apim.core.api.exception;

import io.gravitee.apim.core.exception.ValidationDomainException;

public class DuplicateApiMetadataNameException extends ValidationDomainException {

public DuplicateApiMetadataNameException(String apiId, String name) {
super("Name [" + name + "] already exists for API [" + apiId + "]");
}
}
@@ -0,0 +1,25 @@
/*
* Copyright © 2015 The Gravitee team (http://gravitee.io)
*
* 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.
*/
package io.gravitee.apim.core.api.exception;

import io.gravitee.apim.core.exception.ValidationDomainException;

public class InvalidApiMetadataValueException extends ValidationDomainException {

public InvalidApiMetadataValueException(String value, String format) {
super("Invalid value [" + value + "] for format [" + format + "]");
}
}
Expand Up @@ -16,7 +16,7 @@
package io.gravitee.apim.core.api.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import io.gravitee.rest.api.model.MetadataFormat;
import io.gravitee.apim.core.metadata.model.Metadata;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
Expand All @@ -32,7 +32,7 @@ public class ApiMetadata {
String apiId;
String key;
String name;
MetadataFormat format;
Metadata.MetadataFormat format;

@With
String value;
Expand Down
@@ -0,0 +1,35 @@
/*
* Copyright © 2015 The Gravitee team (http://gravitee.io)
*
* 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.
*/
package io.gravitee.apim.core.api.model;

import io.gravitee.apim.core.metadata.model.Metadata;
import lombok.*;

@Data
@Builder(toBuilder = true)
@AllArgsConstructor
@NoArgsConstructor
public class NewApiMetadata {

String apiId;
String key;
String name;

@Builder.Default
Metadata.MetadataFormat format = Metadata.MetadataFormat.STRING;

String value;
}