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}