001/** 002 * Copyright 2005-2015 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.devtools.jpa.eclipselink.conv.parser.visitor; 017 018import japa.parser.ast.CompilationUnit; 019import japa.parser.ast.body.BodyDeclaration; 020import japa.parser.ast.body.ClassOrInterfaceDeclaration; 021import japa.parser.ast.body.FieldDeclaration; 022import org.apache.commons.lang.StringUtils; 023import org.apache.logging.log4j.Logger; 024import org.apache.logging.log4j.LogManager; 025import org.apache.ojb.broker.metadata.DescriptorRepository; 026import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.ParserUtil; 027import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.AnnotationHelper; 028import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.AnnotationResolver; 029import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.VoidVisitorHelper; 030import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.ColumnResolver; 031import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.ConvertResolver; 032import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.CustomizerResolver; 033import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.EntityResolver; 034import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.EnumeratedResolver; 035import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.GeneratedValueResolver; 036import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.IdClassResolver; 037import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.IdResolver; 038import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.JoinColumnResolver; 039import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.JoinColumnsResolver; 040import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.JoinTableResolver; 041import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.LobResolver; 042import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.ManyToManyResolver; 043import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.ManyToOneResolver; 044import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.MappedSuperClassResolver; 045import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.OneToManyResolver; 046import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.OneToOneResolver; 047import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.OrderByResolver; 048import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.PortableSequenceGeneratorResolver; 049import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.PrimaryKeyJoinColumnResolver; 050import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.PrimaryKeyJoinColumnsResolver; 051import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.ResolverUtil; 052import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.TableResolver; 053import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.TemporalResolver; 054import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.TransientResolver; 055import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.resolver.VersionResolver; 056 057import java.util.ArrayList; 058import java.util.Collection; 059import java.util.HashMap; 060import java.util.Map; 061 062/** 063 * For visiting ojb mapped entities and their super classes. 064 */ 065public class EntityVisitor extends OjbDescriptorRepositoryAwareVisitor { 066 private static final Logger LOG = LogManager.getLogger(EntityVisitor.class); 067 068 //warning this can grow rather large and is never cleared out 069 private static final Map<String, Map<String, CompilationUnit>> PROCESSED_CACHE = new HashMap<String, Map<String, CompilationUnit>>(); 070 071 private final VoidVisitorHelper<String> annotationHelper; 072 073 public void setErrorsOnly() { 074 LogManager.getLogger("org.kuali.rice.devtools.jpa.eclipselink.conv"); 075 } 076 077 public EntityVisitor(Collection<DescriptorRepository> descriptorRepositories, Map<String,String> converterMappings, boolean removeExisting, boolean upperCaseDbArtifactNames) { 078 super(descriptorRepositories); 079 System.out.println( "Created new EntityVisitor for JPA Conversion" ); 080 081 LogManager.getLogger("org.kuali.rice.devtools.jpa.eclipselink.conv"); 082 if (converterMappings == null || converterMappings.isEmpty()) { 083 throw new IllegalArgumentException("converterMappings cannot be null or empty"); 084 } 085 086 final Collection<AnnotationResolver> annotations = new ArrayList<AnnotationResolver>(); 087 annotations.add(new EntityResolver(getDescriptorRepositories())); 088 annotations.add(new MappedSuperClassResolver(getDescriptorRepositories())); 089 annotations.add(new TableResolver(getDescriptorRepositories(), upperCaseDbArtifactNames)); 090 annotations.add(new CustomizerResolver(getDescriptorRepositories())); 091 annotations.add(new TransientResolver(getDescriptorRepositories())); 092 annotations.add(new PortableSequenceGeneratorResolver(getDescriptorRepositories(), upperCaseDbArtifactNames)); 093 annotations.add(new GeneratedValueResolver(getDescriptorRepositories(), upperCaseDbArtifactNames)); 094 annotations.add(new IdResolver(getDescriptorRepositories())); 095 annotations.add(new OneToOneResolver(getDescriptorRepositories())); 096 annotations.add(new OneToManyResolver(getDescriptorRepositories())); 097 annotations.add(new PrimaryKeyJoinColumnResolver(getDescriptorRepositories())); 098 annotations.add(new PrimaryKeyJoinColumnsResolver(getDescriptorRepositories())); 099 annotations.add(new ManyToOneResolver(getDescriptorRepositories())); 100 annotations.add(new ManyToManyResolver(getDescriptorRepositories())); 101 annotations.add(new JoinTableResolver(getDescriptorRepositories())); 102 annotations.add(new JoinColumnResolver(getDescriptorRepositories())); 103 annotations.add(new JoinColumnsResolver(getDescriptorRepositories())); 104 annotations.add(new OrderByResolver(getDescriptorRepositories())); 105 annotations.add(new ColumnResolver(getDescriptorRepositories(), upperCaseDbArtifactNames)); 106 annotations.add(new ConvertResolver(getDescriptorRepositories(),converterMappings)); 107 annotations.add(new VersionResolver(getDescriptorRepositories())); 108 annotations.add(new TemporalResolver(getDescriptorRepositories())); 109 annotations.add(new LobResolver(getDescriptorRepositories())); 110 annotations.add(new EnumeratedResolver(getDescriptorRepositories())); 111 annotations.add(new IdClassResolver(getDescriptorRepositories())); 112 113 annotationHelper = new AnnotationHelper(annotations, removeExisting); 114 } 115 116 @Override 117 public void visit(final CompilationUnit n, final String mappedClass) { 118 if (StringUtils.isBlank(mappedClass)) { 119 throw new IllegalArgumentException("mappedClass cannot be blank"); 120 } 121 122 super.visit(n, mappedClass); 123 ParserUtil.sortImports(n.getImports()); 124 125 processedCache(n, mappedClass, PROCESSED_CACHE); 126 } 127 128 @Override 129 public void visit(final ClassOrInterfaceDeclaration n, final String mappedClass) { 130 annotationHelper.visitPre(n, mappedClass); 131 132 ParserUtil.deconstructMultiDeclarations(ParserUtil.getFieldMembers(n.getMembers())); 133 134 if (n.getMembers() != null) { 135 for (final BodyDeclaration member : n.getMembers()) { 136 member.accept(this, mappedClass); 137 } 138 } 139 140 annotationHelper.visitPost(n, mappedClass); 141 } 142 143 @Override 144 public void visit(final FieldDeclaration n, final String mappedClass) { 145 annotationHelper.visitPre(n, mappedClass); 146 147 //insert logic here if needed 148 149 annotationHelper.visitPost(n, mappedClass); 150 } 151 152 /** 153 * When there is a common super class with multiple subclasses there is a potential for different mapping configurations 154 * on the super class's fields (attributes, references, collections). This is because the subclass's mapping metadata is 155 * used to determine how to map the super class. 156 * 157 * This method is designed to log a message when these types on conflicts are detected during conversion. The differences 158 * are not logged and must be manually evaluated on a case by case basis. 159 * 160 * This is an error situation that must be resolved in order to properly map and entity. You cannot have 161 * one attribute in a superclass as @Transient for one subclass while mapped to a @Column for another subclass. 162 * 163 * When this cases arise you will likely need do one of the following: 164 * 165 * 1) use the @AssociationOverride and/or @AttributeOverride in a subclass 166 * 2) modify the database table structure to make a uniform mapping possible, modify the ojb mapping to 167 * 3) move certain fields out of the superclass into subclasses 168 * 4) create new superclasses to make it possible to have correct mapping 169 * 170 * @param n the compilation unit, already modified by the visitor 171 * @param mappedClass the mapped class who's metadata was used to annotate the compilation unit 172 * @param cache the cache that stores compilation unit information 173 */ 174 private void processedCache(CompilationUnit n, String mappedClass, Map<String, Map<String, CompilationUnit>> cache) { 175 final String enclosingName = n.getPackage().getName() + "." + n.getTypes().get(0).getName(); 176 177 if (!enclosingName.equals(mappedClass)) { 178 Map<String, CompilationUnit> entries = cache.get(enclosingName); 179 if (entries == null) { 180 entries = new HashMap<String, CompilationUnit>(); 181 entries.put(mappedClass, n); 182 cache.put(enclosingName, entries); 183 } else { 184 if (!equalsAny(n, mappedClass, entries, enclosingName)) { 185 //put this unique version of the AST in the cache... don't bother storing equal versions 186 entries.put(mappedClass, n); 187 cache.put(enclosingName, entries); 188 } 189 } 190 } 191 } 192 193 private boolean equalsAny(CompilationUnit n, String mappedClass, Map<String, CompilationUnit> entries, String enclosingName) { 194 boolean equalsAny = false; 195 for (Map.Entry<String, CompilationUnit> entry : entries.entrySet()) { 196 if (!entry.getValue().equals(n)) { 197 LOG.error(ResolverUtil.logMsgForClass(enclosingName, mappedClass) + " does not equal the modified AST for " + ResolverUtil.logMsgForClass(enclosingName, entry.getKey()) + 198 ". This likely means that a super class' fields have different mapping configurations across mapped subclasses."); 199 } else { 200 equalsAny = true; 201 } 202 } 203 204 return equalsAny; 205 } 206}