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