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.helper.resolver; 017 018import japa.parser.ast.ImportDeclaration; 019import japa.parser.ast.expr.ArrayInitializerExpr; 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.QualifiedNameExpr; 025import japa.parser.ast.expr.StringLiteralExpr; 026import org.apache.commons.lang.ClassUtils; 027import org.apache.commons.lang.StringUtils; 028import org.apache.commons.logging.Log; 029import org.apache.commons.logging.LogFactory; 030import org.apache.ojb.broker.metadata.ClassDescriptor; 031import org.apache.ojb.broker.metadata.CollectionDescriptor; 032import org.apache.ojb.broker.metadata.DescriptorRepository; 033import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor; 034import org.kuali.rice.devtools.jpa.eclipselink.conv.ojb.OjbUtil; 035import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.NodeData; 036 037import java.util.ArrayList; 038import java.util.Collection; 039import java.util.Collections; 040import java.util.HashMap; 041import java.util.List; 042import java.util.Map; 043 044public class ManyToManyResolver extends AbstractMappedFieldResolver { 045 private static final Log LOG = LogFactory.getLog(ManyToManyResolver.class); 046 047 public static final String PACKAGE = "javax.persistence"; 048 public static final String SIMPLE_NAME = "ManyToMany"; 049 050 public ManyToManyResolver(Collection<DescriptorRepository> descriptorRepositories) { 051 super(descriptorRepositories); 052 } 053 @Override 054 public String getFullyQualifiedName() { 055 return PACKAGE + "." + SIMPLE_NAME; 056 } 057 058 /** gets the annotation but also adds an import in the process if a Convert annotation is required. */ 059 @Override 060 protected NodeData getAnnotationNodes(String enclosingClass, String fieldName, String mappedClass) { 061 final CollectionDescriptor cld = OjbUtil.findCollectionDescriptor(mappedClass, fieldName, descriptorRepositories); 062 if (cld != null) { 063 final List<MemberValuePair> pairs = new ArrayList<MemberValuePair>(); 064 final Collection<ImportDeclaration> additionalImports = new ArrayList<ImportDeclaration>(); 065 066 if (!cld.isMtoNRelation()) { 067 return null; 068 } 069 070 boolean fkError = false; 071 final String[] fkToItemClass = getFksToItemClass(cld); 072 if (fkToItemClass == null || fkToItemClass.length == 0) { 073 LOG.error(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has a collection descriptor for " + fieldName 074 + " for a M:N relationship but does not have any fk-pointing-to-element-class configured"); 075 fkError = true; 076 } 077 078 final String[] fkToThisClass = getFksToThisClass(cld); 079 if (fkToThisClass == null || fkToThisClass.length == 0) { 080 LOG.error(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has a collection descriptor for " + fieldName 081 + " for a M:N relationship but does not have any fk-pointing-to-this-class configured"); 082 fkError = true; 083 } 084 085 if (fkError) { 086 return null; 087 } 088 089 final Collection<String> fks = cld.getForeignKeyFields(); 090 if (fks != null || !fks.isEmpty()) { 091 LOG.warn(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has a collection descriptor for " + fieldName 092 + " for a M:N relationship but has the inverse-foreignkey configured as opposed to " 093 + "fk-pointing-to-this-class and fk-pointing-to-element-class"); 094 } 095 096 final String itemClassName = cld.getItemClassName(); 097 if (StringUtils.isBlank(itemClassName)) { 098 LOG.error(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has a reference descriptor for " + fieldName 099 + " but does not class name attribute"); 100 } else { 101 final String shortClassName = ClassUtils.getShortClassName(itemClassName); 102 final String packageName = ClassUtils.getPackageName(itemClassName); 103 pairs.add(new MemberValuePair("targetEntity", new NameExpr(shortClassName + ".class"))); 104 additionalImports.add(new ImportDeclaration(new QualifiedNameExpr(new NameExpr(packageName), shortClassName), false, false)); 105 } 106 107 final boolean proxy = cld.isLazy(); 108 if (proxy) { 109 pairs.add(new MemberValuePair("fetch", new NameExpr("FetchType.LAZY"))); 110 additionalImports.add(new ImportDeclaration(new QualifiedNameExpr(new NameExpr(PACKAGE), "FetchType"), false, false)); 111 } 112 113 final boolean refresh = cld.isRefresh(); 114 if (refresh) { 115 LOG.error(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has refresh set to " + refresh + ", unsupported conversion to @OneToOne attributes"); 116 } 117 118 final List<Expression> cascadeTypes = new ArrayList<Expression>(); 119 final boolean autoRetrieve = cld.getCascadeRetrieve(); 120 if (autoRetrieve) { 121 cascadeTypes.add(new NameExpr("CascadeType.REFRESH")); 122 } else { 123 // updated default logging - false would result no additional annotations 124 if ( LOG.isDebugEnabled() ) { 125 LOG.debug(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has auto-retrieve set to " + autoRetrieve + ", unsupported conversion to CascadeType"); 126 } 127 } 128 129 final int autoDelete = cld.getCascadingDelete(); 130 if (autoDelete == ObjectReferenceDescriptor.CASCADE_NONE) { 131 // updated default logging - none would result no additional annotations 132 if ( LOG.isDebugEnabled() ) { 133 LOG.debug(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has auto-delete set to none, unsupported conversion to CascadeType"); 134 } 135 } else if (autoDelete == ObjectReferenceDescriptor.CASCADE_LINK) { 136 LOG.warn(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has auto-delete set to link, unsupported conversion to CascadeType"); 137 } else if (autoDelete == ObjectReferenceDescriptor.CASCADE_OBJECT) { 138 cascadeTypes.add(new NameExpr("CascadeType.REMOVE")); 139 } else { 140 LOG.error(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has auto-delete set to an invalid value"); 141 } 142 143 final int autoUpdate = cld.getCascadingStore(); 144 if (autoUpdate == ObjectReferenceDescriptor.CASCADE_NONE) { 145 // updated default logging - none would result no additional annotations 146 if ( LOG.isDebugEnabled() ) { 147 LOG.debug(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has auto-update set to none, unsupported conversion to CascadeType"); 148 } 149 } else if (autoUpdate == ObjectReferenceDescriptor.CASCADE_LINK) { 150 LOG.warn(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has auto-update set to link, unsupported conversion to CascadeType"); 151 } else if (autoUpdate == ObjectReferenceDescriptor.CASCADE_OBJECT) { 152 cascadeTypes.add(new NameExpr("CascadeType.PERSIST")); 153 } else { 154 LOG.error(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has auto-update set to an invalid value"); 155 } 156 157 if (!cascadeTypes.isEmpty()) { 158 pairs.add(new MemberValuePair("cascade", new ArrayInitializerExpr(cascadeTypes))); 159 additionalImports.add(new ImportDeclaration(new QualifiedNameExpr(new NameExpr(PACKAGE), "CascadeType"), false, false)); 160 } 161 162 final NodeData nodeData; 163 if (isBidirectional(mappedClass, itemClassName)) { 164 LOG.info(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " bi-directional ManyToMany relationship detected"); 165 166 BidirectionalOwnerRegistry registry = BidirectionalOwnerRegistry.getInstance(); 167 if (registry.isOwnerThisClassManyToMany(mappedClass, itemClassName)) { 168 nodeData = new NodeData(new NormalAnnotationExpr(new NameExpr(SIMPLE_NAME), pairs), 169 new ImportDeclaration(new QualifiedNameExpr(new NameExpr(PACKAGE), SIMPLE_NAME), false, false), 170 additionalImports); 171 } else if (registry.isOwnerItemClassManyToMany(mappedClass, itemClassName)) { 172 nodeData = new NodeData(new NormalAnnotationExpr(new NameExpr(SIMPLE_NAME), 173 Collections.singletonList(new MemberValuePair("mappedBy", new StringLiteralExpr(getMappedBy(mappedClass, itemClassName))))), 174 new ImportDeclaration(new QualifiedNameExpr(new NameExpr(PACKAGE), SIMPLE_NAME), false, false), 175 additionalImports); 176 } else { 177 registry.assignThisClassAsOwnerManyToMany(mappedClass, itemClassName); 178 179 nodeData = new NodeData(new NormalAnnotationExpr(new NameExpr(SIMPLE_NAME), pairs), 180 new ImportDeclaration(new QualifiedNameExpr(new NameExpr(PACKAGE), SIMPLE_NAME), false, false), 181 additionalImports); 182 } 183 } else { 184 nodeData = new NodeData(new NormalAnnotationExpr(new NameExpr(SIMPLE_NAME), pairs), 185 new ImportDeclaration(new QualifiedNameExpr(new NameExpr(PACKAGE), SIMPLE_NAME), false, false), 186 additionalImports); 187 } 188 189 return nodeData; 190 } 191 return null; 192 } 193 194 private String[] getFksToItemClass(CollectionDescriptor cld) { 195 try { 196 return cld.getFksToItemClass(); 197 } catch (NullPointerException e) { 198 return new String[] {}; 199 } 200 } 201 202 private String [] getFksToThisClass(CollectionDescriptor cld) { 203 try { 204 return cld.getFksToItemClass(); 205 } catch (NullPointerException e) { 206 return new String[] {}; 207 } 208 } 209 210 private boolean isBidirectional(String thisClass, String itemClass) { 211 final ClassDescriptor cd = OjbUtil.findClassDescriptor(itemClass, descriptorRepositories); 212 Collection<CollectionDescriptor> clds = cd.getCollectionDescriptors(); 213 if (clds != null) { 214 for (CollectionDescriptor cld : clds) { 215 if (cld.getItemClassName().equals(thisClass)) { 216 return true; 217 } 218 } 219 } 220 return false; 221 } 222 223 private String getMappedBy(String thisClass, String itemClass) { 224 final ClassDescriptor cd = OjbUtil.findClassDescriptor(itemClass, descriptorRepositories); 225 Collection<CollectionDescriptor> clds = cd.getCollectionDescriptors(); 226 if (clds != null) { 227 for (CollectionDescriptor cld : clds) { 228 if (cld.getItemClassName().equals(thisClass)) { 229 return cld.getAttributeName(); 230 } 231 } 232 } 233 return null; 234 } 235}