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}