org.joinedworkz.common.helper.OpenApiHelper.xtend Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of common-base Show documentation
Show all versions of common-base Show documentation
DSL based modeling framework - facilities common base
package org.joinedworkz.common.helper
import java.util.List
import java.util.Map
import java.util.Set
import javax.inject.Inject
import javax.inject.Singleton
import org.joinedworkz.common.adapter.ComponentResourcesAdapter
import org.joinedworkz.common.adapter.MergedOperationInfoAdapterForOperation
import org.joinedworkz.common.adapter.MergedOperationInfosAdapter
import org.joinedworkz.common.adapter.ResourceProvideAdapter
import org.joinedworkz.common.context.CommonGeneratorContext
import org.joinedworkz.common.info.MergedResourceOperationInfo
import org.joinedworkz.common.info.ResourceOperationInfo
import org.joinedworkz.common.info.ResponseInfo
import org.joinedworkz.core.model.CmnComplexType
import org.joinedworkz.core.model.CmnComponent
import org.joinedworkz.core.model.CmnContent
import org.joinedworkz.core.model.CmnElement
import org.joinedworkz.core.model.CmnModel
import org.joinedworkz.core.model.CmnObject
import org.joinedworkz.core.model.CmnOperationParameter
import org.joinedworkz.core.model.CmnProvidedResource
import org.joinedworkz.core.model.CmnResource
import org.joinedworkz.core.model.CmnResourceOperation
import org.joinedworkz.core.model.CmnResponse
import org.joinedworkz.core.model.CmnService
import org.joinedworkz.core.model.CmnType
import org.joinedworkz.core.model.Verb
import java.util.function.UnaryOperator
import org.joinedworkz.core.model.CmnProperty
import org.joinedworkz.common.info.MergedResourceOperationPart
import java.util.ArrayList
@Singleton
class OpenApiHelper {
final static char SLASH = '/'
@Inject
extension NameHelper
@Inject
extension CmnModelHelper
@Inject
extension VariableHelper
@Inject
Inflector inflector;
def boolean isMultiple(CmnContent content) {
if (content !== null) {
if (content.maxOccurs !== null) {
return content.maxOccurs < 0 || content.maxOccurs > 1
}
}
return false
}
def String fullQualifiedTypeName(CmnType type) {
val schemaName = type.schemaName
if (schemaName.contains('.')) {
'''«type.schemaName»'''
} else {
'''«type.model.schemaNamespace».«type.schemaName»'''
}
}
def generateContentType(CmnType defaultRepresentation, CmnType representation, String contentType, boolean multiple, boolean hasContext) {
val contentTypes = generateContentTypes(defaultRepresentation, representation, contentType, multiple, hasContext, false)
return contentTypes?.head
}
def Set generateContentTypes(CmnType defaultRepresentation, CmnType representation, String contentType, boolean multiple, boolean hasContext, boolean additionallyWithDefault) {
if (representation instanceof CmnComplexType) {
if (!hasContext && defaultRepresentation == representation) {
if (contentType !== null && !contentType.startsWith("*")) {
return #{contentType}
} else if (contentType == "**") {
return #{"text/plain"} // identifier
} else {
if (additionallyWithDefault) {
val contentTypeForRepresentation = vendorSpecificContentType(representation, multiple)
if (contentTypeForRepresentation !== null) {
return #{'application/json', contentTypeForRepresentation}
}
} else {
return #{'application/json'}
}
}
} else if (representation !== null) {
val contentTypeForRepresentation = vendorSpecificContentType(representation, multiple)
if (contentTypeForRepresentation !== null) {
return #{contentTypeForRepresentation}
}
}
} if (contentType !== null) {
if (!contentType.startsWith("*")) {
return #{contentType}
} else {
return null
}
} else {
return #{'text/plain'}
}
}
def responseContentTypeFor(MergedResourceOperationPart operationPart, CmnType representedEntity) {
if (operationPart.produces?.type !== null) {
return operationPart.produces?.type
}
}
def createResponseInfo(CmnResponse response, CmnResourceOperation operation) {
val responseInfo = new ResponseInfo(operation, response)
responseInfo.responseContext = operation.getResponseContext
responseInfo.responseWrapper = operation.getString("responseWrapper")
return responseInfo
}
def CmnComplexType getResponseContext(CmnResourceOperation operation) {
val fieldFilters = operation.getProperty("responseContext")
if (fieldFilters !== null) {
val value = fieldFilters.value
if (value instanceof CmnComplexType) {
return value
}
}
}
def String simpleRepresentationTypeName(CmnType type, boolean returnsMultiple) {
if (type instanceof CmnComplexType) {
return returnsMultiple ? inflector.pluralize(type.schemaName) : type.schemaName
}
}
def schemaNamespace(CmnModel model) {
val schemaName = model.getString("schemaNamespace")
if (schemaName !== null) {
return schemaName
} else {
return model.namespace
}
}
def vendorSpecificContentType(CmnType representation, boolean multiple) {
val representationSchemaName = simpleRepresentationTypeName(representation, multiple)
if (representationSchemaName !== null) {
val resourceModel = representation.model
return vendorSpecificContentType(resourceModel.schemaNamespace, representationSchemaName)
}
}
def String vendorSpecificContentType(String namespace, String typeName) {
var lastDotIndex = typeName.lastIndexOf('.')
if (lastDotIndex >= 0 ) {
var namespacePart = typeName.substring(0,lastDotIndex)
var namePart = typeName.substring(lastDotIndex+1)
return 'application/vnd.'+namespacePart+'.'+camelCaseToDashSeperated(namePart).toLowerCase+'+json'
} else {
return 'application/vnd.'+namespace+'.'+camelCaseToDashSeperated(typeName).toLowerCase+'+json'
}
}
def void createProvidedResourceAdapters(Iterable components) {
for (component : components) {
createProvidedResourceAdaptersForComponent(component)
}
}
def Set createProvidedResourceAdaptersForComponent(CmnComponent component) {
var componentResourcesAdapter = component.getAdapter(ComponentResourcesAdapter)
val resourcesForApi = newLinkedHashSet
for (providedResource : component.features.filter(CmnProvidedResource) ) {
val headResource = providedResource.resourcePath.head
val lastResource = providedResource.resourcePath.last
var resourceProvidedAdapter = lastResource.getAdapter(ResourceProvideAdapter)
if (resourceProvidedAdapter === null) {
resourceProvidedAdapter = new ResourceProvideAdapter()
lastResource.putAdapter(resourceProvidedAdapter)
}
resourceProvidedAdapter.putProvidedResource(component, providedResource)
resourcesForApi.add(headResource)
}
if (componentResourcesAdapter === null) {
componentResourcesAdapter = new ComponentResourcesAdapter()
component.putAdapter(componentResourcesAdapter)
}
componentResourcesAdapter.resourcesForApi = resourcesForApi
return componentResourcesAdapter.resourcesForApi
}
def Map> createMergedResourceOperationInfos(CmnComponent component, CmnResource it, CommonGeneratorContext ctx) {
return createMergedResourceOperationInfos(component, it, true, ctx)
}
def determineControllerTag(CmnProvidedResource it) {
val controllerName = composeControllerName
if (controllerName !== null) {
val lastDotIndex = controllerName.lastIndexOf('.')
if (lastDotIndex >= 0){
val shortName = controllerName.substring(lastDotIndex + 1)
return shortName.camelCaseToDashSeperated
} else {
return controllerName.camelCaseToDashSeperated
}
}
}
def composeControllerName(CmnProvidedResource providedResource) {
var controllerName = providedResource.getQualifiedControllerNameFromProperty
if (controllerName === null) {
val lastResourceInPath = providedResource.resourcePath.last
val singularResourceName = inflector.singularize(lastResourceInPath.name)
controllerName = singularResourceName.dashSeperatedToCamelCase.toFirstUpper + "Controller"
}
return controllerName
}
def getQualifiedControllerNameFromProperty(CmnElement it) {
val controller = getProperty('controller')
if (controller !== null && controller.value !== null) {
val controllerValue = controller.value
if (controllerValue instanceof CmnService) {
return controllerValue.qualifiedName
} else {
return controllerValue.toString
}
}
}
def CmnType determineRepresentation(CmnResourceOperation it) {
val representationProperty = it.getProperty("representation")
if (representationProperty?.value instanceof CmnType) {
return representationProperty.value as CmnType
}
if (container instanceof CmnResource) {
val resource = container as CmnResource
return resource.determineRepresentation
}
}
def CmnType determineRepresentation(CmnResource it) {
if (representation !== null) {
return representation
} else {
parentResource?.determineRepresentation
}
}
def CmnResource getContainingResource(CmnObject it) {
val containingResource = parentResource
if (containingResource === null) {
throw new RuntimeException("No parent found for resource: ")
}
}
def CmnResource parentResource(CmnObject it) {
if (container instanceof CmnResource) {
return container as CmnResource
} else if (container !== null) {
parentResource(container)
}
}
def CmnProvidedResource getProvidedResource(CmnResource it, CmnComponent component) {
if (it !== null) {
val resourceProvideAdapter = getAdapter(ResourceProvideAdapter)
if (resourceProvideAdapter !== null) {
return resourceProvideAdapter.getProvidedResource(component)
}
}
}
def Map> createMergedResourceOperationInfos(CmnComponent component, CmnResource it, boolean recursive, CommonGeneratorContext ctx) {
try {
val Map> collectedOperationInfos = newLinkedHashMap
collectedOperationInfos.collectOperationInfos("", "", newArrayList, component, it, null, recursive)
return mergeOperations(collectedOperationInfos, ctx)
} catch (Exception ex) {
throw new RuntimeException("Failed to create merged operation infos for component: " + component.name, ex)
}
}
def List resourceAndSubResources(CmnResource it) {
val resources = newArrayList
addResourcesRecursive(resources, it)
return resources
}
def void addResourcesRecursive(List resources, CmnResource it) {
resources.add(it)
if (subResources !== null) {
for (subResource : subResources) {
addResourcesRecursive(resources, subResource)
}
}
}
def void collectOperationInfos(Map> operationInfos, String parentPath, String idPrefix, List parentPathParameters, CmnComponent component, CmnResource resource, CmnProvidedResource providedResourceOfParent, boolean recursive) {
val pathParameters = resource.formattedPathParameters
val providedSubResource = getProvidedResource(resource, component)
val providedResource = providedSubResource !== null ? providedSubResource : providedResourceOfParent
val collectionOperations = resource.operations?.filter[!instanceOperation].iterableToList
operationInfos.addOperations(resource, providedResource, collectionOperations, parentPath, idPrefix, pathParameters, parentPathParameters)
if (recursive) {
val collectionSubResources = resource.subResources?.filter[instanceResource === null || !instanceResource].iterableToList
if (collectionSubResources !== null) {
collectionSubResources.forEach[
val subIdPrefix = composeSubIdPrefix(resource, idPrefix, false)
val forInstanceOperation = resource.name === null
val relativePath = composeRelativePath(resource, pathParameters, forInstanceOperation)
operationInfos.collectOperationInfos(mergePaths(parentPath, relativePath), subIdPrefix, parentPathParameters, component, it, providedResource, recursive)
]
}
}
val instanceOperations = resource.operations?.filter[instanceOperation].iterableToList
operationInfos.addOperations(resource, providedResource, instanceOperations, parentPath, idPrefix, pathParameters, parentPathParameters)
if (recursive) {
val instanceSubResources = resource.subResources?.filter[instanceResource !== null && instanceResource].iterableToList
if (!instanceSubResources.empty) {
val List currentPathParameters = newArrayList
currentPathParameters.addAll(parentPathParameters)
if (resource.identifiers !== null) {
currentPathParameters.addAll(resource.identifiers)
}
instanceSubResources.forEach[
val subIdPrefix = composeSubIdPrefix(resource, idPrefix, true)
val relativePath = composeRelativePath(resource, pathParameters, true)
operationInfos.collectOperationInfos(mergePaths(parentPath,relativePath), subIdPrefix, currentPathParameters, component, it, providedResource, recursive)
]
}
}
}
def subPathForPath(String rootPath, String path) {
if (path !== null) {
val rootPathWithLeadingSlash = "/" + rootPath
var subPathWithLeadingSlash = path
if (path.startsWith(rootPathWithLeadingSlash)) {
subPathWithLeadingSlash = path.substring(rootPathWithLeadingSlash.length)
}
var remainingSubPath = subPathWithLeadingSlash
if (subPathWithLeadingSlash.startsWith("/")) {
remainingSubPath = subPathWithLeadingSlash.substring(1)
}
if (!remainingSubPath.empty) {
return remainingSubPath
}
}
}
def schemaName(CmnType type) {
val schemaName = type.getString("schemaName")
if (schemaName !== null) {
return schemaName
} else {
return type.name
}
}
def formattedPathParameters(CmnResource it) {
if (it.identifiers !== null && !identifiers.empty) {
'''«FOR identifier : identifiers»/{«identifierName(identifier)»}«ENDFOR»'''
} else {
return ''
}
}
def identifierName(CmnOperationParameter it) {
if (name !== null) {
return name
} else if (referencedField !== null) {
return referencedField.name
}
}
def void addOperations(Map> operationInfos, CmnResource resource, CmnProvidedResource providedResource, List operations, String parentPath, String idPrefix, CharSequence formattedPathParameters, List parentPathParameters) {
for (operation : operations) {
val operationInfo = createOperationInfo(parentPath, idPrefix, resource, providedResource, formattedPathParameters, parentPathParameters, operation)
var operationInfoList = operationInfos.get(operationInfo.path)
if (operationInfoList === null) {
operationInfoList = newArrayList
operationInfos.put(operationInfo.path, operationInfoList)
}
operationInfoList.add(operationInfo)
}
}
def CharSequence generateOperationId(MergedResourceOperationInfo it) {
val operationId = determineOperationId
'''operationId: «operationId»'''
}
def CmnType determineRepresentedEntity(MergedResourceOperationInfo mergedOperationInfo) {
val operation = mergedOperationInfo.operations.head
return operation.determineRepresentation
}
def CmnProperty getOperationProperty(MergedResourceOperationInfo mergedOperationInfo, String propertyName) {
val operation = mergedOperationInfo.operations.head
return operation.getProperty(propertyName)
}
def String determineOperationName(MergedResourceOperationInfo it, MergedResourceOperationPart operationPart) {
val explicitName = operationPart.operation.name
if (explicitName !== null) {
return explicitName
}
val operationNameProperty = operationPart.operation.getProperty('operationName')?.value?.toString
if (operationNameProperty !== null) {
if (operationNameProperty.indexOf('$') > -1) {
val variables = new UnaryOperator() {
override apply(String variableName) {
val plural = variableName.endsWith("[]")
val postProcessedVariableName = plural ? variableName.substring(0, variableName.length-2) : variableName
switch (postProcessedVariableName) {
case "entity": {
val typeName = determineRepresentedEntity().name
if (plural) {
return inflector.pluralize(typeName)
} else {
return typeName
}
}
case "resource": return resource.name?.toFirstUpper
case "consumes": {
val typeName = operationPart.consumes?.type?.name
if (plural) {
return inflector.pluralize(typeName)
} else {
return typeName
}
}
case "produces": {
val typeName = operationPart.produces?.type?.name
if (plural) {
return inflector.pluralize(typeName)
} else {
return typeName
}
}
}
}
}
return operationNameProperty.replaceVariables(variables)
} else {
return operationNameProperty
}
}
val operationId = determineIndividualOperationId(operationPart)
if (operationId !== null) {
return operationId.lastSegment
}
return 'do'
}
def String determineOperationId(MergedResourceOperationInfo it) {
return determineIndividualOperationId(null)
}
def String determineIndividualOperationId(MergedResourceOperationInfo it, MergedResourceOperationPart operationPart) {
val boolean takeRepresentationName = resource.isCollectionResource && !returnsMultiple
val operationIdProperty = getProperty('operationId')
val CmnResourceOperation operation = operationPart !== null ? operationPart.operation : operations?.head
val String operationName = operation.name
val CmnContent consumes = operationPart !== null && operationPart.consumes !== null ? operationPart.consumes : consumes.head
val operationType = operationPart !== null ? operation.operationType : it.operationType
if (operationIdProperty !== null) {
var operationId = operationIdProperty.value.toString
if (operationId.indexOf('$') > -1) {
val variables = new UnaryOperator() {
override apply(String variableName) {
switch (variableName) {
case "entity": return determineRepresentedEntity().name
case "resource": return resource.name?.toFirstUpper
case "consumes": {
if (consumes !== null && consumes.type !== null) {
return consumes.type.name
} else {
val producesProperty = operation.getProperty("produces")
if (producesProperty !== null) {
return producesProperty.value as String
}
}
}
case "produces": {
if (operation !== null && operation.responses !== null) {
val content = operation.responses?.head?.content
if (content !== null && content.type !== null) {
return content.type.name
}
}
}
}
}
}
operationId = operationId.replaceVariables(variables)
}
return operationId
}
if (operationName !== null) {
return operationName
}
val resourceName = resource.name !== null ? resource.name : 'By' + resource.composeResourceName.toFirstUpper
val namePostFix = if (takeRepresentationName) resource.representation.name else resourceName
val name = operationType + namePostFix.toFirstUpper.charSeperatedToCamelCase('-', true)
return if (idPrefix !== null && idPrefix.length > 0) idPrefix+"."+name else name
}
/* just for debugging */
def String stringRepresentation(MergedResourceOperationInfo it) {
val operationId = determineOperationId
return path + " " + verb + " (" + operationId + ")"
}
def boolean isCollectionResource(CmnResource it) {
return it !== null && maxOccurs !== null && maxOccurs !== 1
}
def composeSubIdPrefix(CmnResource resource, String parentIdPrefix, boolean instanceOperation) {
val name = if (instanceOperation && resource.isCollectionResource) resource.representation.name else composeResourceName(resource)
if (parentIdPrefix !== null && parentIdPrefix.length > 0) parentIdPrefix + "." + name else name
}
def composeResourceName(CmnResource resource) {
if (resource.name !== null) {
return resource.name
} else if (resource.identifiers !== null) {
val buffer = new StringBuilder
var first = true
for (identifier : resource.identifiers) {
if (first) {
first = false
} else {
buffer.append('-')
}
buffer.append(identifier.name)
}
return buffer.toString
}
}
def Map> mergeOperations(Map> map, CommonGeneratorContext ctx) {
val Map> mergedInfos = newLinkedHashMap
if (map !== null) {
for (entry : map.entrySet) {
val path = entry.key
val Map mergedInfosForVerb = newLinkedHashMap
for (info : entry.value) {
var mergedInfo = mergedInfosForVerb.get(info.verb)
if (mergedInfo === null) {
mergedInfo = new MergedResourceOperationInfo(info)
mergedInfo.addParentPathParameters(info.parentParameters)
mergedInfosForVerb.put(info.verb, mergedInfo)
}
/* support multiple consumes (=types of request bodies) */
if (info.operation.consumes !== null) {
mergedInfo.consumes.addAll(info.operation.consumes)
}
mergedInfo.addOperation(info.operation)
if (!info.operation.hasAdapter(MergedOperationInfoAdapterForOperation)) {
// System.out.println("Put adapter for: " + info.operation + " resource name: " + info.resource.name + " verb: " + info.verb.name+ " path: "+info.path)
info.operation.putAdapter(new MergedOperationInfoAdapterForOperation(mergedInfo)) // allow operation to reference mergedInfo
}
}
val mergedOperationInfoList = mergedInfosForVerb.values.toList
for (mergedOperationInfo : mergedOperationInfoList) {
mergedOperationInfo.operationParts.addAll(createMergedResourceOperationParts(mergedOperationInfo))
mergedOperationInfo.setup()
}
mergedInfos.put(path, mergedOperationInfoList)
/* create adapter for merged operation infos and add operation infos */
for (mergedOperationInfo : mergedOperationInfoList) {
val resource = mergedOperationInfo.resource
var adapter = resource.getAdapter(MergedOperationInfosAdapter)
if (adapter === null) {
adapter = new MergedOperationInfosAdapter()
resource.putAdapter(adapter)
ctx.addObjectWithAdatper(resource)
}
adapter.operationInfos.add(mergedOperationInfo)
}
}
}
return mergedInfos
}
def List createMergedResourceOperationParts(MergedResourceOperationInfo mergedResourceOperationInfo) {
if (mergedResourceOperationInfo.operations !== null && !mergedResourceOperationInfo.operations.empty) {
val operationParts = new ArrayList()
for (operation : mergedResourceOperationInfo.operations) {
val hasConsumes = operation.consumes !== null && !operation.consumes.empty
val hasResponses = operation.responses !== null && !operation.responses.empty
val producesOfOperation = hasResponses ? operation.responses.filter[statusCode < 300 && content !== null].map[content] : emptyList
val hasProduces = !producesOfOperation.empty
if (hasConsumes) {
for (consumes : operation.consumes) {
if (hasProduces) {
for (produces : producesOfOperation) {
if (!produces.derivedFromRepresentation) {
val part = createMergedResourceOperationPart(operation, consumes, produces);
operationParts.add(part)
} else if (!consumes.derivedFromRepresentation || consumes.type === produces.type) {
val part = createMergedResourceOperationPart(operation, consumes, produces);
operationParts.add(part)
}
}
} else {
val part = createMergedResourceOperationPart(operation, consumes, null);
operationParts.add(part)
}
}
} else if (hasProduces) {
for (produces : producesOfOperation) {
val part = createMergedResourceOperationPart(operation, null, produces);
operationParts.add(part)
}
} else {
val part = createMergedResourceOperationPart(operation, null, null);
operationParts.add(part)
}
}
return operationParts
}
return emptyList
}
def MergedResourceOperationPart createMergedResourceOperationPart(CmnResourceOperation operation, CmnContent consumes, CmnContent produces) {
return new MergedResourceOperationPart(operation, consumes, produces)
}
def MergedOperationInfoAdapterForOperation(MergedResourceOperationInfo info) {
throw new UnsupportedOperationException("TODO: auto-generated method stub")
}
def getMergedOperationInfos(CmnResource resource) {
val adapter = resource.getAdapter(MergedOperationInfosAdapter)
return adapter !== null ? adapter.operationInfos : emptyList
}
def createOperationInfo(String parentPath, String idPrefix, CmnResource resource, CmnProvidedResource providedResource, CharSequence pathParameters, List parentPathParameters, CmnResourceOperation operation) {
val operationInfo = new ResourceOperationInfo(parentPath, operation, idPrefix)
val forInstanceOperation = operation?.instanceOperation || resource.name === null
val relativePath = composeRelativePath(resource, pathParameters, forInstanceOperation)
operationInfo.path = mergePaths(parentPath,relativePath)
if (operationInfo.path === null) {
throw new RuntimeException("OperationInfo path is null")
}
operationInfo.resource = resource
operationInfo.providedResource = providedResource
operationInfo.addParentPathParameters(parentPathParameters)
return operationInfo
}
def composeRelativePath(CmnResource it, CharSequence pathParameters, boolean forInstanceOperation) {
val resourcePath = pathSegment
if (resourcePath === null) {
return forInstanceOperation ? pathParameters : ""
} else {
return forInstanceOperation ? resourcePath + pathParameters : resourcePath
}
}
def String mergePaths(CharSequence parentPath, CharSequence subPath) {
if (subPath !== null && subPath.length > 0) {
if (subPath.charAt(0) === SLASH) {
return parentPath + subPath.toString
} else {
return parentPath + "/" + subPath
}
} else {
return parentPath.toString
}
}
def String pathSegment(CmnResource it) {
return name
}
def iterableToList(Iterable it) {
if (it !== null) {
toList
} else {
emptyList
}
}
}