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.BlockComment; 019import japa.parser.ast.Comment; 020import japa.parser.ast.ImportDeclaration; 021import japa.parser.ast.LineComment; 022import japa.parser.ast.expr.ArrayInitializerExpr; 023import japa.parser.ast.expr.BooleanLiteralExpr; 024import japa.parser.ast.expr.Expression; 025import japa.parser.ast.expr.MemberValuePair; 026import japa.parser.ast.expr.NameExpr; 027import japa.parser.ast.expr.NormalAnnotationExpr; 028import japa.parser.ast.expr.QualifiedNameExpr; 029import japa.parser.ast.expr.StringLiteralExpr; 030import org.apache.commons.lang.ClassUtils; 031import org.apache.commons.lang.StringUtils; 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.apache.ojb.broker.metadata.ClassDescriptor; 035import org.apache.ojb.broker.metadata.DescriptorRepository; 036import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor; 037import org.kuali.rice.devtools.jpa.eclipselink.conv.ojb.OjbUtil; 038import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.NodeData; 039 040import java.util.ArrayList; 041import java.util.Collection; 042import java.util.List; 043 044public class OneToOneResolver extends AbstractMappedFieldResolver { 045 private static final Log LOG = LogFactory.getLog(OneToOneResolver.class); 046 047 public static final String PACKAGE = "javax.persistence"; 048 public static final String SIMPLE_NAME = "OneToOne"; 049 050 public OneToOneResolver(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 ObjectReferenceDescriptor ord = OjbUtil.findObjectReferenceDescriptor(mappedClass, fieldName, descriptorRepositories); 062 if (ord != null) { 063 final List<MemberValuePair> pairs = new ArrayList<MemberValuePair>(); 064 final Collection<ImportDeclaration> additionalImports = new ArrayList<ImportDeclaration>(); 065 066 final Collection<String> fks = ord.getForeignKeyFields(); 067 if (fks == null || fks.isEmpty()) { 068 LOG.error(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has a reference descriptor for " + fieldName 069 + " but does not have any foreign keys configured"); 070 return null; 071 } 072 073 final Collection<String> pks = OjbUtil.getPrimaryKeyNames(mappedClass, descriptorRepositories); 074 075 if (pks.size() == fks.size() && pks.containsAll(fks)) { 076 final String itemClassName = ord.getItemClassName(); 077 if (StringUtils.isBlank(itemClassName)) { 078 LOG.error(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has a reference descriptor for " + fieldName 079 + " but does not class name attribute"); 080 } else { 081 final String shortClassName = ClassUtils.getShortClassName(itemClassName); 082 final String packageName = ClassUtils.getPackageName(itemClassName); 083 pairs.add(new MemberValuePair("targetEntity", new NameExpr(shortClassName + ".class"))); 084 additionalImports.add(new ImportDeclaration(new QualifiedNameExpr(new NameExpr(packageName), shortClassName), false, false)); 085 } 086 087 final boolean proxy = ord.isLazy(); 088 if (proxy) { 089 pairs.add(new MemberValuePair("fetch", new NameExpr("FetchType.LAZY"))); 090 additionalImports.add(new ImportDeclaration(new QualifiedNameExpr(new NameExpr(PACKAGE), "FetchType"), false, false)); 091 } 092 093 final boolean refresh = ord.isRefresh(); 094 if (refresh) { 095 LOG.error(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has refresh set to " + refresh + ", unsupported conversion to @OneToOne attributes"); 096 } 097 098 final List<Expression> cascadeTypes = new ArrayList<Expression>(); 099 final boolean autoRetrieve = ord.getCascadeRetrieve(); 100 if (autoRetrieve) { 101 cascadeTypes.add(new NameExpr("CascadeType.REFRESH")); 102 } else { 103 LOG.warn(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has auto-retrieve set to " + autoRetrieve + ", unsupported conversion to CascadeType"); 104 } 105 106 final int autoDelete = ord.getCascadingDelete(); 107 if (autoDelete == ObjectReferenceDescriptor.CASCADE_NONE) { 108 LOG.warn(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has auto-delete set to none, unsupported conversion to CascadeType"); 109 } else if (autoDelete == ObjectReferenceDescriptor.CASCADE_LINK) { 110 LOG.warn(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has auto-delete set to link, unsupported conversion to CascadeType"); 111 } else if (autoDelete == ObjectReferenceDescriptor.CASCADE_OBJECT) { 112 cascadeTypes.add(new NameExpr("CascadeType.REMOVE")); 113 pairs.add(new MemberValuePair("orphanRemoval", new BooleanLiteralExpr(true))); 114 } else { 115 LOG.error(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has auto-delete set to an invalid value"); 116 } 117 118 final int autoUpdate = ord.getCascadingStore(); 119 if (autoUpdate == ObjectReferenceDescriptor.CASCADE_NONE) { 120 LOG.warn(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has auto-update set to none, unsupported conversion to CascadeType"); 121 } else if (autoUpdate == ObjectReferenceDescriptor.CASCADE_LINK) { 122 LOG.warn(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has auto-update set to link, unsupported conversion to CascadeType"); 123 } else if (autoUpdate == ObjectReferenceDescriptor.CASCADE_OBJECT) { 124 cascadeTypes.add(new NameExpr("CascadeType.PERSIST")); 125 } else { 126 LOG.error(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " field has auto-update set to an invalid value"); 127 } 128 129 if (!cascadeTypes.isEmpty()) { 130 pairs.add(new MemberValuePair("cascade", new ArrayInitializerExpr(cascadeTypes))); 131 additionalImports.add(new ImportDeclaration(new QualifiedNameExpr(new NameExpr(PACKAGE), "CascadeType"), false, false)); 132 } 133 final NodeData nodeData; 134 if (isBidirectional(mappedClass, itemClassName)) { 135 LOG.info(ResolverUtil.logMsgForField(enclosingClass, fieldName, mappedClass) + " bi-directional OneToOne relationship detected"); 136 137 final String mappedBy = getMappedBy(mappedClass, itemClassName); 138 final Comment fixme = new BlockComment("\nFIXME: JPA_CONVERSION \n" 139 + "For one-to-one bidirectional relationships, the owning side corresponds to the side that contains the corresponding foreign key.\n" 140 + "Even in the absence of a foreign key, one side must be the owning side.\n" 141 + "If this is not the owning side, the required annotation should be:\n@OneToOne(mappedBy=\"" + mappedBy + "\")\n"); 142 final NormalAnnotationExpr annotation = new NormalAnnotationExpr(new NameExpr(SIMPLE_NAME), pairs); 143 annotation.setComment(fixme); 144 nodeData = new NodeData(annotation, 145 new ImportDeclaration(new QualifiedNameExpr(new NameExpr(PACKAGE), SIMPLE_NAME), false, false), 146 additionalImports); 147 148 } else { 149 nodeData = new NodeData(new NormalAnnotationExpr(new NameExpr(SIMPLE_NAME), pairs), 150 new ImportDeclaration(new QualifiedNameExpr(new NameExpr(PACKAGE), SIMPLE_NAME), false, false), 151 additionalImports); 152 } 153 return nodeData; 154 } 155 } 156 return null; 157 } 158 159 private boolean isBidirectional(String thisClass, String itemClass) { 160 final ClassDescriptor cd = OjbUtil.findClassDescriptor(itemClass, descriptorRepositories); 161 if (cd != null) { 162 Collection<ObjectReferenceDescriptor> ords = cd.getObjectReferenceDescriptors(); 163 if (ords != null) { 164 for (ObjectReferenceDescriptor ord : ords) { 165 if (ord.getItemClassName().equals(thisClass)) { 166 return true; 167 } 168 } 169 } 170 } 171 return false; 172 } 173 174 private String getMappedBy(String thisClass, String itemClass) { 175 final ClassDescriptor cd = OjbUtil.findClassDescriptor(itemClass, descriptorRepositories); 176 if (cd != null) { 177 Collection<ObjectReferenceDescriptor> ords = cd.getObjectReferenceDescriptors(); 178 if (ords != null) { 179 for (ObjectReferenceDescriptor ord : ords) { 180 if (ord.getItemClassName().equals(thisClass)) { 181 return ord.getAttributeName(); 182 } 183 } 184 } 185 } 186 return null; 187 } 188}