org.babyfish.jimmer.client.runtime.impl.MetadataBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jimmer-client Show documentation
Show all versions of jimmer-client Show documentation
A revolutionary ORM framework for both java and kotlin
package org.babyfish.jimmer.client.runtime.impl;
import org.babyfish.jimmer.client.meta.*;
import org.babyfish.jimmer.client.meta.impl.ApiServiceImpl;
import org.babyfish.jimmer.client.meta.impl.SchemaImpl;
import org.babyfish.jimmer.client.meta.impl.Schemas;
import org.babyfish.jimmer.client.meta.impl.TypeDefinitionImpl;
import org.babyfish.jimmer.client.runtime.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;
public class MetadataBuilder implements Metadata.Builder {
private Metadata.OperationParser operationParser;
private Metadata.ParameterParser parameterParser;
private Set groups;
private boolean genericSupported;
private String uriPrefix;
private boolean controllerNullityChecked;
private Map virtualTypeMap = Collections.emptyMap();
private Set> ignoredParameterTypes = new LinkedHashSet<>();
private Set> illegalReturnTypes = new LinkedHashSet<>();
@Override
public Metadata.Builder setOperationParser(Metadata.OperationParser operationParser) {
this.operationParser = operationParser;
return this;
}
@Override
public Metadata.Builder setParameterParameter(Metadata.ParameterParser parameterParser) {
this.parameterParser = parameterParser;
return this;
}
@Override
public Metadata.Builder setGroups(Collection groups) {
if (groups != null && !groups.isEmpty()) {
Set set = new HashSet<>((groups.size() * 4 + 2) / 3);
for (String group : groups) {
String trim = group.trim();
if (!trim.isEmpty()) {
set.add(trim);
}
this.groups = set.isEmpty() ? null : set;
}
} else {
this.groups = null;
}
return this;
}
@Override
public Metadata.Builder setGenericSupported(boolean genericSupported) {
this.genericSupported = genericSupported;
return this;
}
@Override
public Metadata.Builder setUriPrefix(String uriPrefix) {
this.uriPrefix = uriPrefix;
return this;
}
public Metadata.Builder setControllerNullityChecked(boolean controllerNullityChecked) {
this.controllerNullityChecked = controllerNullityChecked;
return this;
}
@Override
public MetadataBuilder setVirtualTypeMap(Map virtualTypeMap) {
this.virtualTypeMap =
virtualTypeMap != null && !virtualTypeMap.isEmpty() ?
virtualTypeMap :
Collections.emptyMap();
return this;
}
@Override
public Metadata.Builder addIgnoredParameterTypes(Class> ... types) {
ignoredParameterTypes.addAll(Arrays.asList(types));
return this;
}
@Override
public Metadata.Builder addIllegalReturnTypes(Class>... types) {
illegalReturnTypes.addAll(Arrays.asList(types));
return this;
}
@Override
public Metadata build() {
if (operationParser == null) {
throw new IllegalStateException("Operation parse has not been set");
}
if (parameterParser == null) {
throw new IllegalStateException("ParameterParser parse has not been set");
}
Schema schema = loadSchema(groups);
TypeContext ctx = new TypeContext(schema.getTypeDefinitionMap(), virtualTypeMap, genericSupported);
List services = new ArrayList<>();
for (ApiService apiService : schema.getApiServiceMap().values()) {
services.add(service(apiService, ctx));
}
List fetchedTypes = new ArrayList<>(ctx.fetchedTypes());
List dynamicTypes = new ArrayList<>(ctx.dynamicTypes());
List embeddableTypes = new ArrayList<>(ctx.embeddableTypes());
List staticTypes = new ArrayList<>();
for (StaticObjectTypeImpl staticObjectType : ctx.staticTypes()) {
if (staticObjectType.unwrap() == null) {
staticTypes.add(staticObjectType);
}
}
List enumTypes = new ArrayList<>(ctx.enumTypes());
return new MetadataImpl(
genericSupported,
Collections.unmodifiableList(services),
Collections.unmodifiableList(fetchedTypes),
Collections.unmodifiableList(dynamicTypes),
Collections.unmodifiableList(embeddableTypes),
Collections.unmodifiableList(staticTypes),
Collections.unmodifiableList(enumTypes)
);
}
@SuppressWarnings("unchecked")
public static Schema loadSchema(Set groups) {
Map> serviceMap = new LinkedHashMap<>();
Map> definitionMap = new LinkedHashMap<>();
try {
Enumeration urls = Thread.currentThread().getContextClassLoader().getResources("META-INF/jimmer/client");
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()))) {
Schema schema = Schemas.readFrom(reader, groups);
for (ApiService service : schema.getApiServiceMap().values()) {
serviceMap.putIfAbsent(service.getTypeName(), (ApiServiceImpl) service);
}
for (TypeDefinition definition : schema.getTypeDefinitionMap().values()) {
definitionMap.putIfAbsent(definition.getTypeName(), (TypeDefinitionImpl) definition);
}
} catch (IOException ex) {
throw new IllegalStateException("Failed to load resources \"" + url + "\"", ex);
}
}
} catch (IOException ex) {
throw new IllegalStateException("Failed to load resources \"META-INF/jimmer/client\"", ex);
}
return new SchemaImpl<>(serviceMap, definitionMap);
}
private ServiceImpl service(ApiService apiService, TypeContext ctx) {
ServiceImpl service = new ServiceImpl(ctx.javaType(apiService.getTypeName()));
service.setDoc(apiService.getDoc());
String baseUri = operationParser.uri(service.getJavaType());
if (uriPrefix != null && !uriPrefix.isEmpty()) {
baseUri = concatUri(uriPrefix, baseUri);
}
Map endpointMap = new HashMap<>();
Map operationMap = new IdentityHashMap<>((apiService.getOperations().size() * 4 + 2) / 3);
for (Method method : service.getJavaType().getMethods()) {
ApiOperation apiOperation = apiService.findOperation(method.getName(), method.getParameterTypes());
if (apiOperation != null) {
OperationImpl operation = operation(service, apiOperation, method, baseUri, ctx);
operationMap.put(apiOperation, operation);
for (Operation.HttpMethod httpMethod : operation.getHttpMethods()) {
String endpoint = httpMethod.name() + ':' + operation.getUri();
Operation conflictOperation = endpointMap.put(endpoint, operation);
if (conflictOperation != null) {
throw new IllegalApiException(
"Conflict endpoint \"" +
endpoint +
"\" which is shared by \"" +
conflictOperation.getJavaMethod() +
"\" and \"" +
operation.getJavaMethod() +
"\""
);
}
}
}
}
List operations = new ArrayList<>(apiService.getOperations().size());
for (ApiOperation apiOperation : apiService.getOperations()) {
Operation operation = operationMap.get(apiOperation);
if (operation != null) {
operations.add(operation);
}
}
service.setOperations(Collections.unmodifiableList(operations));
return service;
}
private OperationImpl operation(Service service, ApiOperation apiOperation, Method method, String baseUri, TypeContext ctx) {
OperationImpl operation = new OperationImpl(service, method);
String uri = operationParser.uri(method);
operation.setUri(concatUri(baseUri, uri));
operation.setDoc(apiOperation.getDoc());
operation.setHttpMethods(operationParser.http(method));
Parameter[] javaParameters = method.getParameters();
List parameters = new ArrayList<>();
for (ApiParameter apiParameter : apiOperation.getParameters()) {
if (!ignoredParameterTypes.contains(javaParameters[apiParameter.getOriginalIndex()].getType())) {
parameters.add(parameter(apiParameter, javaParameters[apiParameter.getOriginalIndex()], method, ctx));
}
}
boolean hasRequestBody = false;
boolean hasRequestPart = false;
for (org.babyfish.jimmer.client.runtime.Parameter parameter : parameters) {
if (parameter.isRequestBody()) {
if (hasRequestBody) {
throw new IllegalApiException(
"Illegal method \"" +
method +
"\", it can't have more than one request body parameter"
);
}
hasRequestBody = true;
}
hasRequestPart |= parameter.getRequestPart() != null;
if (hasRequestBody && hasRequestPart) {
throw new IllegalApiException(
"Illegal method \"" +
method +
"\", It can't have both request body and request part parameters"
);
}
}
operation.setParameters(Collections.unmodifiableList(parameters));
if (apiOperation.getReturnType() != null) {
if (illegalReturnTypes.contains(method.getReturnType())) {
throw new IllegalApiException(
"Illegal method \"" +
method +
"\", The client API does not support the operation return type \"" +
method.getReturnType().getName() +
"\", please change the return type or add `@ApiIgnore` to the current operation"
);
}
operation.setReturnType(ctx.parseType(apiOperation.getReturnType()));
}
operation.setExceptionTypes(
apiOperation
.getExceptionTypes()
.stream()
.map(it -> (ObjectType) ctx.parseType(it))
.collect(Collectors.toList())
);
return operation;
}
private ParameterImpl parameter(ApiParameter apiParameter, Parameter javaParameter, Method method, TypeContext ctx) {
ParameterImpl parameter = new ParameterImpl(apiParameter.getName());
String requestHeader = parameterParser.requestHeader(javaParameter);
String requestParam = parameterParser.requestParam(javaParameter);
String pathVariable = parameterParser.pathVariable(javaParameter);
String requestPart = parameterParser.requestPart(javaParameter);
boolean isRequestBody = parameterParser.isRequestBody(javaParameter);
Set parameterKinds = new LinkedHashSet<>();
if (requestHeader != null) {
parameterKinds.add("request header");
}
if (requestParam != null) {
parameterKinds.add("request parameter");
}
if (pathVariable != null) {
parameterKinds.add("path variable");
}
if (requestPart != null) {
parameterKinds.add("request part");
}
if (isRequestBody) {
parameterKinds.add("request body");
}
if (parameterKinds.size() > 1) {
throw new IllegalApiException(
"Illegal API method \"" +
method +
"\", its parameter \"" +
apiParameter.getName() +
"\" cannot be both " + parameterKinds
);
}
if (requestHeader != null) {
if (requestHeader.isEmpty()) {
parameter.setRequestHeader(apiParameter.getName());
} else {
parameter.setRequestHeader(requestHeader);
}
} else if (requestParam != null) {
if (requestParam.isEmpty()) {
parameter.setRequestParam(apiParameter.getName());
} else {
parameter.setRequestParam(requestParam);
}
} else if (pathVariable != null) {
if (pathVariable.isEmpty()) {
parameter.setPathVariable(apiParameter.getName());
} else {
parameter.setPathVariable(pathVariable);
}
} else if (requestPart != null) {
if (requestPart.isEmpty()) {
parameter.setRequestPart(apiParameter.getName());
} else {
parameter.setRequestPart(requestPart);
}
} else if (isRequestBody) {
parameter.setRequestBody(true);
} else if (!apiParameter.getType().getTypeName().isGenerationRequired()) {
throw new IllegalApiException(
"Illegal API method \"" +
method +
"\", its parameter \"" +
apiParameter.getName() +
"\" is not simple type, but its neither request param nor " +
"path variable nor request body"
);
}
String defaultValue = parameterParser.defaultValue(javaParameter);
parameter.setDefaultValue(defaultValue);
Type type = ctx.parseType(apiParameter.getType());
if (requestHeader != null && !NullableTypeImpl.unwrap(type).equals(SimpleTypeImpl.of(TypeName.STRING))) {
throw new IllegalApiException(
"Illegal API method \"" +
method +
"\", its parameter \"" +
apiParameter.getName() +
"\" is http header parameter but its type is not string"
);
}
if (parameterParser.isOptional(javaParameter)) {
type = NullableTypeImpl.of(type);
} else if (controllerNullityChecked && apiParameter.getType().isNullable() && defaultValue == null) {
throw new IllegalApiException(
"Illegal API method \"" +
method +
"\", its parameter \"" +
apiParameter.getName() +
"\" is nullable but The web framework thinks " +
"it's neither null nor has a default value"
);
}
parameter.setType(type);
return parameter;
}
private static String concatUri(String baseUri, String uri) {
if (baseUri == null) {
baseUri = "";
}
if (uri == null) {
uri = "";
}
if (baseUri.endsWith("/") && uri.startsWith("/")) {
return baseUri + uri.substring(1);
}
if (!baseUri.endsWith("/") && !uri.startsWith("/")) {
return baseUri + '/' + uri;
}
return baseUri + uri;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy