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}