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.helper.resolver;
017
018import japa.parser.ast.expr.AnnotationExpr;
019import japa.parser.ast.expr.BooleanLiteralExpr;
020import japa.parser.ast.expr.Expression;
021import japa.parser.ast.expr.MemberValuePair;
022import japa.parser.ast.expr.NameExpr;
023import japa.parser.ast.expr.NormalAnnotationExpr;
024import japa.parser.ast.expr.StringLiteralExpr;
025import org.apache.commons.lang.StringUtils;
026import org.apache.logging.log4j.Logger;
027import org.apache.logging.log4j.LogManager;
028import org.apache.ojb.broker.metadata.ClassDescriptor;
029import org.apache.ojb.broker.metadata.CollectionDescriptor;
030import org.apache.ojb.broker.metadata.DescriptorRepository;
031import org.apache.ojb.broker.metadata.FieldDescriptor;
032import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
033import org.kuali.rice.devtools.jpa.eclipselink.conv.ojb.OjbUtil;
034
035import java.util.ArrayList;
036import java.util.Collection;
037import java.util.Collections;
038import java.util.List;
039
040public abstract class AbstractJoinColumnResolver extends AbstractMappedFieldResolver {
041    private static final Logger LOG = LogManager.getLogger(AbstractJoinColumnResolver.class);
042
043    public AbstractJoinColumnResolver(Collection<DescriptorRepository> descriptorRepositories) {
044        super(descriptorRepositories);
045    }
046
047    protected final List<Expression> getJoinColumns(String enclosingClass, String fieldName, String mappedClass) {
048        final ObjectReferenceDescriptor ord = OjbUtil.findObjectReferenceDescriptor(mappedClass, fieldName,
049                descriptorRepositories);
050
051        final CollectionDescriptor cld = OjbUtil.findCollectionDescriptor(mappedClass, fieldName,
052                descriptorRepositories);
053
054        if (ord != null) {
055            return processReferenceField(enclosingClass, fieldName, mappedClass, ord);
056        } else if (cld != null) {
057            return processCollectionField(enclosingClass, fieldName, mappedClass, cld);
058        }
059
060        return Collections.emptyList();
061    }
062
063    private List<Expression> processReferenceField(String enclosingClass, String fieldName, String mappedClass, ObjectReferenceDescriptor ord) {
064        final List<Expression> joinColumns = new ArrayList<Expression>();
065        final Collection<String> fks = ord.getForeignKeyFields();
066        if (fks == null || fks.isEmpty()) {
067            LOG.error(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has a reference descriptor for " + fieldName
068                    + " but does not have any foreign keys configured");
069            return joinColumns;
070        }
071
072        final Collection<String> pks = OjbUtil.getPrimaryKeyNames(mappedClass, descriptorRepositories);
073
074        //make sure it isn't a one to one
075        if (!(pks.containsAll(fks) && fks.containsAll(pks)) && !pks.isEmpty()) {
076
077            final ClassDescriptor cd = OjbUtil.findClassDescriptor(mappedClass, descriptorRepositories);
078            final ClassDescriptor icd = getItemClassDescriptor(enclosingClass, fieldName, mappedClass, ord);
079            final FieldDescriptor[] fkDescs = ord.getForeignKeyFieldDescriptors(cd);
080            final FieldDescriptor[] pkDescs = icd.getPkFields();
081
082            if (fkDescs.length != pkDescs.length) {
083                LOG.error(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has a collection descriptor for " + fieldName
084                        + " with an foreign key that is not joined to all of the primary key fields. This is not supported in JPA.");
085            }
086
087            for (int i = 0; i < fkDescs.length; i ++) {
088                joinColumns.add(createJoinColumn(fkDescs[i], pkDescs[i]));
089            }
090        }
091        return joinColumns;
092    }
093
094    private List<Expression> processCollectionField(String enclosingClass, String fieldName, String mappedClass, CollectionDescriptor cld) {
095        final List<Expression> joinColumns = new ArrayList<Expression>();
096
097        if (!cld.isMtoNRelation()) {
098            final Collection<String> fks = cld.getForeignKeyFields();
099            if (fks == null || fks.isEmpty()) {
100                LOG.error(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has a collection descriptor for " + fieldName
101                        + " but does not have any inverse foreign keys configured");
102                return joinColumns;
103            }
104
105            final ClassDescriptor cd = OjbUtil.findClassDescriptor(mappedClass, descriptorRepositories);
106            final ClassDescriptor icd = getItemClassDescriptor(enclosingClass, fieldName, mappedClass, cld);
107            final FieldDescriptor[] fkDescs =  cld.getForeignKeyFieldDescriptors(icd);
108            final FieldDescriptor[] pkDescs = cd.getPkFields();
109
110            if (fkDescs.length != pkDescs.length) {
111                LOG.error(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has a collection descriptor for " + fieldName
112                + " with an inverse foreign key that is not joined to all of the primary key fields.  This is not supported in JPA.");
113            }
114
115            for (int i = 0; i < fkDescs.length; i ++) {
116                joinColumns.add(createJoinColumn(pkDescs[i], fkDescs[i]));
117            }
118        }
119        return joinColumns;
120    }
121
122    private AnnotationExpr createJoinColumn(FieldDescriptor thisField, FieldDescriptor itemField) {
123        final List<MemberValuePair> pairs = new ArrayList<MemberValuePair>();
124
125        pairs.add(new MemberValuePair("name", new StringLiteralExpr(thisField.getColumnName())));
126        pairs.add(new MemberValuePair("referencedColumnName", new StringLiteralExpr(itemField.getColumnName())));
127        if (!isAnonymousFk(thisField)) {
128            pairs.add(new MemberValuePair("insertable", new BooleanLiteralExpr(false)));
129            pairs.add(new MemberValuePair("updatable", new BooleanLiteralExpr(false)));
130        }
131
132        // Per this page: https://forums.oracle.com/message/3923913
133        // the nullable attribute is a hint to the DDL generation, especially on fields like this.
134        // Commenting this flag out for now as it's just "noise" in the annotation definitions
135//        if (!isNullableFk(thisField)) {
136//            pairs.add(new MemberValuePair("nullable", new BooleanLiteralExpr(false)));
137//        }
138        return new NormalAnnotationExpr(new NameExpr("JoinColumn"), pairs);
139    }
140
141    private ClassDescriptor getItemClassDescriptor(String enclosingClass, String fieldName, String mappedClass, CollectionDescriptor cld) {
142        return getItemClassDescriptor(enclosingClass, fieldName, mappedClass, cld.getItemClassName());
143    }
144
145    private ClassDescriptor getItemClassDescriptor(String enclosingClass, String fieldName, String mappedClass, ObjectReferenceDescriptor ord) {
146        return getItemClassDescriptor(enclosingClass, fieldName, mappedClass, ord.getItemClassName());
147    }
148
149    private ClassDescriptor getItemClassDescriptor(String enclosingClass, String fieldName, String mappedClass, String itemClassName) {
150        if (StringUtils.isBlank(itemClassName)) {
151            LOG.error(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has a reference descriptor for " + fieldName
152                    + " but does not class name attribute");
153            return null;
154        } else {
155            return OjbUtil.findClassDescriptor(itemClassName, descriptorRepositories);
156        }
157    }
158
159    private boolean isAnonymousFk(FieldDescriptor fd) {
160        if (fd != null) {
161            return "anonymous".equals(fd.getAccess());
162        }
163        return false;
164    }
165
166    private boolean isNullableFk(FieldDescriptor fd) {
167        if (fd != null) {
168            return fd.isRequired();
169        }
170        return false;
171    }
172}