package org.springframework.roo.classpath.operations.jsr303; import static org.springframework.roo.model.JdkJavaType.HASH_SET; import static org.springframework.roo.model.JpaJavaType.ELEMENT_COLLECTION; import static org.springframework.roo.model.JpaJavaType.CASCADE_TYPE; import static org.springframework.roo.model.JpaJavaType.FETCH_TYPE; import static org.springframework.roo.model.JpaJavaType.ONE_TO_MANY; import static org.springframework.roo.model.JpaJavaType.MANY_TO_MANY; import static org.springframework.roo.model.JpaJavaType.ONE_TO_ONE; import org.apache.commons.lang3.StringUtils; import static org.springframework.roo.model.JpaJavaType.MANY_TO_ONE; import static org.springframework.roo.model.JpaJavaType.JOIN_COLUMN; import static org.springframework.roo.model.JpaJavaType.JOIN_COLUMNS; import static org.springframework.roo.model.JpaJavaType.JOIN_TABLE; import java.util.*; import org.springframework.roo.classpath.details.annotations.*; import org.springframework.roo.classpath.operations.*; import org.springframework.roo.model.*; /** * Properties used by the one side of a many-to-one relationship or an @ElementCollection * of enums (called a "set"). * <p> * For example, an Order-LineItem link would have the Order contain a "set" of * Orders. * <p> * Limited support for collection mapping is provided. This reflects the * pragmatic goals of the tool and the fact a user can edit the generated files * by hand anyway. * <p> * This field is intended for use with JSR 220 and will create a @OneToMany * annotation or in the case of enums, an @ElementCollection annotation will be * created. * * @author Ben Alex * @since 1.0 */ public class SetField extends CollectionField { private final Cardinality cardinality; private Fetch fetch; private List<AnnotationMetadataBuilder> additionaAnnotations = new ArrayList<AnnotationMetadataBuilder>(); private Cascade[] cascadeType; private final boolean isDto; private Boolean orphanRemoval; /** * Whether the JSR 220 @OneToMany.mappedBy annotation attribute will be * added */ private JavaSymbolName mappedBy; /** * Constructor for SetField * * @param physicalTypeIdentifier * @param fieldType * @param fieldName * @param genericParameterTypeName * @param cardinality * @param cascadeType * @param isDto whether target class is a DTO */ public SetField(final String physicalTypeIdentifier, final JavaType fieldType, final JavaSymbolName fieldName, final JavaType genericParameterTypeName, final Cardinality cardinality, final Cascade[] cascadeType, final boolean isDto) { super(physicalTypeIdentifier, fieldType, fieldName, genericParameterTypeName); this.cardinality = cardinality; this.cascadeType = cascadeType; this.isDto = isDto; } @Override public void decorateAnnotationsList(final List<AnnotationMetadataBuilder> annotations) { super.decorateAnnotationsList(annotations); final List<AnnotationAttributeValue<?>> attributes = new ArrayList<AnnotationAttributeValue<?>>(); // Manage cardinality only if class is not a DTO if (!isDto) { if (cardinality == null) { // Assume set field is an enum annotations.add(new AnnotationMetadataBuilder(ELEMENT_COLLECTION)); } else { // Add cascade if option exists if (cascadeType != null) { List<EnumAttributeValue> cascadeValues = new ArrayList<EnumAttributeValue>(); for (Cascade type : cascadeType) { cascadeValues.add(new EnumAttributeValue(new JavaSymbolName("cascade"), new EnumDetails(CASCADE_TYPE, new JavaSymbolName(type.name())))); } attributes.add(new ArrayAttributeValue<EnumAttributeValue>(new JavaSymbolName("cascade"), cascadeValues)); } // Add orphanRemoval if option exists if (getOrphanRemoval() != null) { attributes.add(new BooleanAttributeValue(new JavaSymbolName("orphanRemoval"), getOrphanRemoval().booleanValue())); } if (fetch != null) { JavaSymbolName value = new JavaSymbolName("EAGER"); if (fetch == Fetch.LAZY) { value = new JavaSymbolName("LAZY"); } attributes.add(new EnumAttributeValue(new JavaSymbolName("fetch"), new EnumDetails( FETCH_TYPE, value))); } if (mappedBy != null) { attributes.add(new StringAttributeValue(new JavaSymbolName("mappedBy"), mappedBy .getSymbolName())); } switch (cardinality) { case ONE_TO_MANY: annotations.add(new AnnotationMetadataBuilder(ONE_TO_MANY, attributes)); break; case MANY_TO_MANY: annotations.add(new AnnotationMetadataBuilder(MANY_TO_MANY, attributes)); break; case ONE_TO_ONE: annotations.add(new AnnotationMetadataBuilder(ONE_TO_ONE, attributes)); break; default: annotations.add(new AnnotationMetadataBuilder(MANY_TO_ONE, attributes)); break; } } } // Add additional annotations (if any) if (additionaAnnotations != null) { annotations.addAll(additionaAnnotations); } } public Fetch getFetch() { return fetch; } @Override public JavaType getInitializer() { final List<JavaType> params = new ArrayList<JavaType>(); params.add(getGenericParameterTypeName()); return new JavaType(HASH_SET.getFullyQualifiedTypeName(), 0, DataType.TYPE, null, params); } public void addAdditionaAnnotation(AnnotationMetadataBuilder annotationBuilder) { additionaAnnotations.add(annotationBuilder); } public JavaSymbolName getMappedBy() { return mappedBy; } public Boolean getOrphanRemoval() { return orphanRemoval; } public void setFetch(final Fetch fetch) { this.fetch = fetch; } public void setMappedBy(final JavaSymbolName mappedBy) { this.mappedBy = mappedBy; } public void setOrphanRemoval(Boolean orphanRemoval) { this.orphanRemoval = orphanRemoval; } /** * Add @JoinColumn annotation to field * * @param joinColumn * @param referencedColumn */ public void setJoinColumn(String joinColumn, String referencedColumn) { setJoinAnnotations(null, new String[] {joinColumn}, new String[] {referencedColumn}, null, null); } /** * Fill {@link #joinTableAttributes} for building @JoinTable annotation. The annotation * would have some nested @JoinColumn annotations in each of its "joinColumns" and * "inverseJoinColumns" attributes. * * @param joinTableName * @param joinColumns * @param referencedColumns * @param inverseJoinColumns * @param inverseReferencedColumns */ public void setJoinAnnotations(String joinTableName, String[] joinColumns, String[] referencedColumns, String[] inverseJoinColumns, String[] inverseReferencedColumns) { final List<AnnotationMetadataBuilder> joinColumnsBuilders = new ArrayList<AnnotationMetadataBuilder>(); if (joinColumns != null) { // Build joinColumns attribute for (int i = 0; i < joinColumns.length; i++) { // Build @JoinColumn annotation for owner side of the relation final AnnotationMetadataBuilder joinColumnAnnotation = new AnnotationMetadataBuilder(JOIN_COLUMN); joinColumnAnnotation.addStringAttribute("name", joinColumns[i]); joinColumnAnnotation.addStringAttribute("referencedColumnName", referencedColumns[i]); joinColumnsBuilders.add(joinColumnAnnotation); } } final List<AnnotationMetadataBuilder> inverseJoinColumnsBuilders = new ArrayList<AnnotationMetadataBuilder>(); if (inverseJoinColumns != null) { // Build inverseJoinColumns attribute for (int i = 0; i < inverseJoinColumns.length; i++) { // Build @JoinColumn annotation for the not owner side of the relation final AnnotationMetadataBuilder inverseJoinColumnsAnnotation = new AnnotationMetadataBuilder(JOIN_COLUMN); inverseJoinColumnsAnnotation.addStringAttribute("name", inverseJoinColumns[i]); inverseJoinColumnsAnnotation.addStringAttribute("referencedColumnName", inverseReferencedColumns[i]); inverseJoinColumnsBuilders.add(inverseJoinColumnsAnnotation); } } if (StringUtils.isNotBlank(joinTableName) || !inverseJoinColumnsBuilders.isEmpty()) { // add @JoinTable annotation // Add attributes for @JoinTable annotation final List<AnnotationAttributeValue<?>> joinTableAttributes = new ArrayList<AnnotationAttributeValue<?>>(); // If name not specified, use default name value joinTableAttributes.add(new StringAttributeValue(new JavaSymbolName("name"), joinTableName)); // If joinColumns options were not specified, use default @JoinColumn values if (joinColumns != null) { final List<AnnotationAttributeValue<?>> joinColumnsAnnotations = new ArrayList<AnnotationAttributeValue<?>>(); for (AnnotationMetadataBuilder joinColumnAnnotation : joinColumnsBuilders) { joinColumnsAnnotations.add(new NestedAnnotationAttributeValue(new JavaSymbolName( "joinColumns"), joinColumnAnnotation.build())); } joinTableAttributes.add(new ArrayAttributeValue<AnnotationAttributeValue<?>>( new JavaSymbolName("joinColumns"), joinColumnsAnnotations)); } // If inverseJoinColumns options were not specified, use default @JoinColumn values if (inverseJoinColumns != null) { final List<AnnotationAttributeValue<?>> inverseJoinColumnsAnnotations = new ArrayList<AnnotationAttributeValue<?>>(); for (AnnotationMetadataBuilder inverseJoinColumnsAnnotation : inverseJoinColumnsBuilders) { inverseJoinColumnsAnnotations.add(new NestedAnnotationAttributeValue(new JavaSymbolName( "inverseJoinColumns"), inverseJoinColumnsAnnotation.build())); } joinTableAttributes.add(new ArrayAttributeValue<AnnotationAttributeValue<?>>( new JavaSymbolName("inverseJoinColumns"), inverseJoinColumnsAnnotations)); } // Add @JoinTable to additonalAnnotations additionaAnnotations.add(new AnnotationMetadataBuilder(JOIN_TABLE, joinTableAttributes)); } else if (!joinColumnsBuilders.isEmpty()) { // Manage @JoinColumn if (joinColumnsBuilders.size() == 1) { // Just one @JoinColumn additionaAnnotations.add(joinColumnsBuilders.iterator().next()); } else { // Multiple @JoinColumn, wrap with @JoinColumns final AnnotationMetadataBuilder joinColumnsAnnotation = new AnnotationMetadataBuilder(JOIN_COLUMNS); final List<AnnotationAttributeValue<?>> joinColumnsAnnotations = new ArrayList<AnnotationAttributeValue<?>>(); for (AnnotationMetadataBuilder joinColumnAnnotation : joinColumnsBuilders) { joinColumnsAnnotations.add(new NestedAnnotationAttributeValue( new JavaSymbolName("value"), joinColumnAnnotation.build())); } joinColumnsAnnotation.addAttribute(new ArrayAttributeValue<AnnotationAttributeValue<?>>( new JavaSymbolName("value"), joinColumnsAnnotations)); // Add @JoinColumns additionaAnnotations.add(joinColumnsAnnotation); } } } }