in grails-datamapping-core/src/main/groovy/org/grails/compiler/gorm/GormEntityTransformation.groovy [151:378]
void visit(ClassNode classNode, SourceUnit sourceUnit) {
if (classNode.getNodeMetaData(AstUtils.TRANSFORM_APPLIED_MARKER) == APPLIED_MARKER) {
return
}
if ((classNode instanceof InnerClassNode) || classNode.isEnum()) {
// do not apply transform to enums or inner classes
return
}
if (hasAnnotation(classNode, ClassHelper.make(Embeddable))) {
// do not apply transform to embeddedable JPA classes
return
}
final boolean isJpaEntity = hasAnnotation(classNode, JPA_ENTITY_CLASS_NODE)
AstUtils.addTransformedEntityName(classNode.name)
// Add the entity annotation and enable generic replacement
classNode.setUsingGenerics(true)
if (!isJpaEntity) {
AstUtils.addAnnotationIfNecessary(classNode, Entity.class)
try {
AstUtils.addAnnotationIfNecessary(classNode, (Class<? extends Annotation>) getClass().classLoader.loadClass('grails.persistence.Entity'))
} catch (Throwable e) {
try {
def cl = Thread.currentThread().contextClassLoader
AstUtils.addAnnotationIfNecessary(classNode, (Class<? extends Annotation>) Class.forName('grails.persistence.Entity', true, cl))
} catch (Throwable e2) {
// Only GORM classes on the classpath continue
}
}
}
// Add the Jackson @JsonIgnoreProperties if Jackson is present
// Add @JsonIgnoreProperties(['dirtyPropertyNames', 'errors', 'dirty', 'attached', 'version'])
if (ClassUtils.isPresent("com.fasterxml.jackson.annotation.JsonIgnoreProperties")) {
AnnotationNode ignorePropertiesAnn = AstUtils.addAnnotationOrGetExisting(classNode, (Class<? extends Annotation>) getClass().classLoader.loadClass('com.fasterxml.jackson.annotation.JsonIgnoreProperties'))
Expression existing = ignorePropertiesAnn.getMember("value")
if (existing == null) {
ignorePropertiesAnn.setMember("value", IGNORED_PROPERTIES)
} else {
if (existing instanceof ListExpression) {
ListExpression listExpression = (ListExpression) existing
for (exp in IGNORED_PROPERTIES.expressions) {
listExpression.addExpression(exp)
}
}
}
}
def rxEntityClassNode = AstUtils.findInterface(classNode, "grails.gorm.rx.RxEntity")
boolean isRxEntity = rxEntityClassNode != null
if (!isJpaEntity) {
// Add default id
injectIdProperty(classNode)
}
if (!isRxEntity && !isJpaEntity) {
// Add version
injectVersionProperty(classNode)
}
// inject toString()
if (!isJpaEntity) {
injectToStringMethod(classNode)
}
// inject the GORM entity trait unless it is an RX entity
MethodNode addToMethodNode = ADD_TO_METHOD_NODE
MethodNode removeFromMethodNode = REMOVE_FROM_METHOD_NODE
MethodNode getAssociationMethodNode = GET_ASSOCIATION_ID_METHOD_NODE
if (!isRxEntity) {
def classGormEntityTrait = pickGormEntityTrait(classNode, sourceUnit)
AstUtils.injectTrait(classNode, classGormEntityTrait)
} else {
addToMethodNode = rxEntityClassNode.getMethods("addTo").get(0)
removeFromMethodNode = rxEntityClassNode.getMethods("removeFrom").get(0)
getAssociationMethodNode = rxEntityClassNode.getMethods("getAssociationId").get(0)
}
// inject associations
if (isJpaEntity) {
injectAssociationsForJpaEntity(classNode, addToMethodNode, removeFromMethodNode, getAssociationMethodNode)
} else {
injectAssociations(classNode, addToMethodNode, removeFromMethodNode, getAssociationMethodNode)
}
// now apply dirty checking behavior
def dirtyCheckTransformer = new DirtyCheckingTransformer()
dirtyCheckTransformer.performInjectionOnAnnotatedClass(sourceUnit, classNode, GormEntityDirtyCheckable)
// convert the methodMissing and propertyMissing implementations to $static_methodMissing and $static_propertyMissing for the static versions
def methodMissingBody = new BlockStatement()
def methodNameParam = new Parameter(ClassHelper.make(String), "name")
def methodArgsParam = new Parameter(AstUtils.OBJECT_CLASS_NODE, "args")
def methodMissingArgs = new ArgumentListExpression(methodNameParam, methodArgsParam)
def methodMissingMethodCall = new MethodCallExpression(new VariableExpression("this"), "staticMethodMissing", methodMissingArgs)
methodMissingBody.addStatement(
new ExpressionStatement(methodMissingMethodCall)
)
def methodMissingParameters = [methodNameParam, methodArgsParam] as Parameter[]
classNode.addMethod('$static_methodMissing', Modifier.PUBLIC | Modifier.STATIC, AstUtils.OBJECT_CLASS_NODE, methodMissingParameters, null, methodMissingBody)
// $static_propertyMissing setter
def propertyMissingSetBody = new BlockStatement()
def propertyMissingSetNameParam = new Parameter(ClassHelper.make(String), "name")
def propertyMissingSetValueParam = new Parameter(AstUtils.OBJECT_CLASS_NODE, "value")
def propertyMissingSetArgs = new ArgumentListExpression(propertyMissingSetNameParam, propertyMissingSetValueParam)
def propertyMissingSetMethodCall = new MethodCallExpression(new VariableExpression("this"), "staticPropertyMissing", propertyMissingSetArgs)
propertyMissingSetBody.addStatement(
new ExpressionStatement(propertyMissingSetMethodCall)
)
def propertyMissingSetParameters = [propertyMissingSetNameParam, propertyMissingSetValueParam] as Parameter[]
classNode.addMethod('$static_propertyMissing', Modifier.PUBLIC | Modifier.STATIC, AstUtils.OBJECT_CLASS_NODE, propertyMissingSetParameters, null, propertyMissingSetBody)
// $static_propertyMissing getter
def propertyMissingGetBody = new BlockStatement()
def propertyMissingGetNameParam = new Parameter(ClassHelper.make(String), "name")
def propertyMissingGetArgs = new ArgumentListExpression(propertyMissingGetNameParam)
def propertyMissingGetMethodCall = new MethodCallExpression(new VariableExpression("this"), "staticPropertyMissing", propertyMissingGetArgs)
propertyMissingGetBody.addStatement(
new ExpressionStatement(propertyMissingGetMethodCall)
)
def propertyMissingGetParameters = [propertyMissingGetNameParam] as Parameter[]
classNode.addMethod('$static_propertyMissing', Modifier.PUBLIC | Modifier.STATIC, AstUtils.OBJECT_CLASS_NODE, propertyMissingGetParameters, null, propertyMissingGetBody)
// now process named query associations
// see https://grails.github.io/grails-doc/latest/ref/Domain%20Classes/namedQueries.html
// for each method call create a named query proxy lookup
def thisClassNode = classNode
def namedQueriesProp = thisClassNode.getProperty(GormProperties.NAMED_QUERIES)
def currentClassNode = classNode
while (namedQueriesProp != null) {
def expression = namedQueriesProp.getInitialExpression()
if (expression instanceof ClosureExpression) {
ClosureExpression ce = (ClosureExpression) expression
def statement = ce.code
if (statement instanceof BlockStatement) {
BlockStatement body = (BlockStatement) statement
def allStatements = body.statements
for (s in allStatements) {
if (s instanceof ExpressionStatement) {
ExpressionStatement es = (ExpressionStatement) s
if (es.expression instanceof MethodCallExpression) {
MethodCallExpression mce = (MethodCallExpression) es.expression
String methodName = mce.getMethodAsString()
Expression argsX = mce.arguments
if (argsX instanceof ArgumentListExpression) {
ArgumentListExpression ale = (ArgumentListExpression) argsX
Expression first = ale.expressions.size() == 1 ? ale.getExpression(0) : null
if (first instanceof ClosureExpression) {
ClosureExpression closureX = (ClosureExpression) first
Parameter[] closureParams = closureX.parameters
boolean hasParameters = closureParams != null && closureParams.length > 0
Parameter[] newParams = hasParameters ? AstUtils.copyParameters(closureParams) : AstUtils.ZERO_PARAMETERS
MethodNode existing = thisClassNode.getMethod(methodName, newParams)
if (existing == null || !existing.getDeclaringClass().equals(thisClassNode)) {
def queryOperationsClassNode = AstUtils.nonGeneric(ClassHelper.make(GormQueryOperations))
final GenericsType[] genericsTypes = queryOperationsClassNode.getGenericsTypes()
final Map<String, ClassNode> parameterNameToParameterValue = new LinkedHashMap<String, ClassNode>()
if (genericsTypes != null) {
for (GenericsType gt : genericsTypes) {
parameterNameToParameterValue.put(gt.getName(), thisClassNode.getPlainNodeReference());
}
}
AstUtils.replaceGenericsPlaceholders(queryOperationsClassNode, parameterNameToParameterValue)
def methodBody = new BlockStatement()
ArgumentListExpression createNamedQueryArgs = args(classX(thisClassNode), constX(methodName))
for (arg in args(newParams)) {
createNamedQueryArgs.addExpression(arg)
}
MethodCallExpression createNamedQueryCall = callX(classX(GormEnhancer), CREATE_NAMED_QUERY, createNamedQueryArgs)
List<MethodNode> createNamedQueryMethods = ClassHelper.makeCached(GormEnhancer).getMethods(CREATE_NAMED_QUERY)
if (!createNamedQueryMethods.isEmpty()) {
createNamedQueryCall.setMethodTarget(createNamedQueryMethods.find() { MethodNode mn -> hasParameters ? mn.parameters.length == 3 : mn.parameters.length == 2 })
}
methodBody.addStatement(returnS(createNamedQueryCall))
MethodNode newMethod = new MethodNode(methodName, Modifier.PUBLIC | Modifier.STATIC, queryOperationsClassNode, newParams, null, methodBody)
thisClassNode.addMethod(newMethod)
if (!hasParameters) {
String namedQueryGetter = NameUtils.getGetterName(methodName)
existing = thisClassNode.getMethod(namedQueryGetter, AstUtils.ZERO_PARAMETERS)
if (existing == null || !existing.getDeclaringClass().equals(thisClassNode)) {
newMethod = new MethodNode(namedQueryGetter, Modifier.PUBLIC | Modifier.STATIC, queryOperationsClassNode, AstUtils.ZERO_PARAMETERS, null, methodBody)
thisClassNode.addMethod(newMethod)
}
}
}
}
}
}
}
}
}
currentClassNode = currentClassNode.getSuperClass()
namedQueriesProp = currentClassNode.getProperty(GormProperties.NAMED_QUERIES)
}
}
def additionalTransforms = ServiceLoader.load(AdditionalGormEntityTransformation, getClass().classLoader)
for (additionalTransform in additionalTransforms) {
if (additionalTransform.isAvailable()) {
additionalTransform.visit(classNode, sourceUnit)
}
}
if (compilationUnit != null && !isRxEntity) {
org.codehaus.groovy.transform.trait.TraitComposer.doExtendTraits(classNode, sourceUnit, compilationUnit);
}
classNode.putNodeMetaData(AstUtils.TRANSFORM_APPLIED_MARKER, APPLIED_MARKER)
}