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