/** * Copyright (c) 2013, Andre Steingress * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * 1.) Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. * 2.) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided with the distribution. * 3.) Neither the name of Andre Steingress nor the names of its contributors may be used to endorse or * promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.gcontracts.grails; import org.codehaus.groovy.ast.*; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.gcontracts.common.base.BaseLifecycle; import org.gcontracts.common.spi.ProcessingContextInformation; import org.gcontracts.generation.CandidateChecks; import org.gcontracts.generation.ClassInvariantGenerator; import org.gcontracts.util.AnnotationUtils; import org.objectweb.asm.Opcodes; /** * Implements a {@link org.gcontracts.common.spi.Lifecycle} in order to intercept GContracts * assertion generation mechanism and adapt it to specific requirements of the Spring * application container.<p/> * * @author ast */ public class SpringContractsLifecycle extends BaseLifecycle { private static final String INITIALIZINGBEAN_INTERFACE = "org.springframework.beans.factory.InitializingBean"; private static final String POSTCONSTRUCT_ANNOTATION = "javax.annotation.PostConstruct"; private static final String POSTCONSTRUCT_METHOD_NAME = "$gcontracts_postConstruct"; private static final String SPRING_STEREOTYPE_PACKAGE = "org.springframework.stereotype"; private static final String IS_SPRING_STEREOTYPE = "isSpringStereotype"; @Override public void beforeProcessingClassNode(ProcessingContextInformation processingContextInformation, ClassNode classNode) { if (!CandidateChecks.isContractsCandidate(classNode)) return; boolean isSpringStereotype = AnnotationUtils.hasAnnotationOfType(classNode, SPRING_STEREOTYPE_PACKAGE); processingContextInformation.put(IS_SPRING_STEREOTYPE, isSpringStereotype); processingContextInformation.setConstructorAssertionsEnabled(!isSpringStereotype); } @Override public void afterProcessingClassNode(ProcessingContextInformation processingContextInformation, ClassNode classNode) { if (!CandidateChecks.isContractsCandidate(classNode)) return; if (!(Boolean) processingContextInformation.get(IS_SPRING_STEREOTYPE)) return; if (processingContextInformation.contract().hasDefaultClassInvariant()) return; createPostConstructMethodForSpringBeans(processingContextInformation, classNode); } private void createPostConstructMethodForSpringBeans(ProcessingContextInformation pci, ClassNode type) { if (isAnyPostConstructionCallbackAvailable(type)) return; // add a synthetic post-construction method final MethodNode postConstructionMethodNode = type.addMethod(POSTCONSTRUCT_METHOD_NAME, Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, ClassHelper.VOID_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, new BlockStatement()); final Class<?> postConstructAnnotationClass; try { postConstructAnnotationClass = SpringContractsLifecycle.class.getClassLoader().loadClass(POSTCONSTRUCT_ANNOTATION); final ClassNode postConstructAnnotationClassNode = ClassHelper.makeWithoutCaching(postConstructAnnotationClass); postConstructionMethodNode.addAnnotation(new AnnotationNode(postConstructAnnotationClassNode)); final ClassInvariantGenerator classInvariantGenerator = new ClassInvariantGenerator(pci.readerSource()); classInvariantGenerator.addInvariantAssertionStatement(type, postConstructionMethodNode); } catch (ClassNotFoundException e) { pci.addError("Annotation " + POSTCONSTRUCT_ANNOTATION + " could not be found in classpath!", type); } } private boolean isAnyPostConstructionCallbackAvailable(ClassNode type) { boolean foundAnyPostConstructionCallback = false; // 1: the bean implements InitializingBean for (ClassNode interfaceClassNode : type.getAllInterfaces()) { if (interfaceClassNode.getName().equals(INITIALIZINGBEAN_INTERFACE)) { foundAnyPostConstructionCallback = true; break; } } // 2: method annotated with @PostConstruct (JEE5) for (MethodNode methodNode : type.getAllDeclaredMethods()) { if (AnnotationUtils.hasAnnotationOfType(methodNode, POSTCONSTRUCT_ANNOTATION)) { foundAnyPostConstructionCallback = true; break; } } return foundAnyPostConstructionCallback; } }