package org.springframework.roo.classpath.operations;
import static org.springframework.roo.model.JdkJavaType.LIST;
import static org.springframework.roo.model.JdkJavaType.SET;
import static org.springframework.roo.model.JpaJavaType.EMBEDDABLE;
import static org.springframework.roo.model.JpaJavaType.ENTITY;
import static org.springframework.roo.model.SpringJavaType.PERSISTENT;
import static org.springframework.roo.shell.OptionContexts.PROJECT;
import static org.springframework.roo.shell.OptionContexts.UPDATE_PROJECT;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.Validate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.osgi.service.component.ComponentContext;
import org.springframework.roo.classpath.PhysicalTypeCategory;
import org.springframework.roo.classpath.PhysicalTypeDetails;
import org.springframework.roo.classpath.PhysicalTypeMetadata;
import org.springframework.roo.classpath.TypeLocationService;
import org.springframework.roo.classpath.TypeManagementService;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.FieldMetadataBuilder;
import org.springframework.roo.classpath.details.MemberHoldingTypeDetails;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
import org.springframework.roo.classpath.details.comments.CommentFormatter;
import org.springframework.roo.classpath.operations.jsr303.BooleanField;
import org.springframework.roo.classpath.operations.jsr303.CollectionField;
import org.springframework.roo.classpath.operations.jsr303.DateField;
import org.springframework.roo.classpath.operations.jsr303.DateFieldPersistenceType;
import org.springframework.roo.classpath.operations.jsr303.EmbeddedField;
import org.springframework.roo.classpath.operations.jsr303.EnumField;
import org.springframework.roo.classpath.operations.jsr303.FieldDetails;
import org.springframework.roo.classpath.operations.jsr303.ListField;
import org.springframework.roo.classpath.operations.jsr303.NumericField;
import org.springframework.roo.classpath.operations.jsr303.ReferenceField;
import org.springframework.roo.classpath.operations.jsr303.SetField;
import org.springframework.roo.classpath.operations.jsr303.StringField;
import org.springframework.roo.classpath.operations.jsr303.UploadedFileContentType;
import org.springframework.roo.classpath.operations.jsr303.UploadedFileField;
import org.springframework.roo.classpath.scanner.MemberDetails;
import org.springframework.roo.classpath.scanner.MemberDetailsScanner;
import org.springframework.roo.metadata.MetadataService;
import org.springframework.roo.model.DataType;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.model.JdkJavaType;
import org.springframework.roo.model.ReservedWords;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.shell.CliAvailabilityIndicator;
import org.springframework.roo.shell.CliCommand;
import org.springframework.roo.shell.CliOption;
import org.springframework.roo.shell.CommandMarker;
import org.springframework.roo.shell.converters.StaticFieldConverter;
/**
* Additional shell commands for the purpose of creating fields.
*
* @author Ben Alex
* @author Alan Stewart
* @since 1.0
*/
@Component
@Service
public class FieldCommands implements CommandMarker {
@Reference private MemberDetailsScanner memberDetailsScanner;
@Reference private MetadataService metadataService;
@Reference private ProjectOperations projectOperations;
@Reference private StaticFieldConverter staticFieldConverter;
@Reference private TypeLocationService typeLocationService;
@Reference private TypeManagementService typeManagementService;
private final Set<String> legalNumericPrimitives = new HashSet<String>();
protected void activate(final ComponentContext context) {
legalNumericPrimitives.add(Short.class.getName());
legalNumericPrimitives.add(Byte.class.getName());
legalNumericPrimitives.add(Integer.class.getName());
legalNumericPrimitives.add(Long.class.getName());
legalNumericPrimitives.add(Float.class.getName());
legalNumericPrimitives.add(Double.class.getName());
staticFieldConverter.add(Cardinality.class);
staticFieldConverter.add(Fetch.class);
staticFieldConverter.add(EnumType.class);
staticFieldConverter.add(DateTime.class);
}
@CliCommand(value = "field boolean", help = "Adds a private boolean field to an existing Java source file")
public void addFieldBoolean(
@CliOption(key = { "", "fieldName" }, mandatory = true, help = "The name of the field to add") final JavaSymbolName fieldName,
@CliOption(key = "class", mandatory = false, unspecifiedDefaultValue = "*", optionContext = UPDATE_PROJECT, help = "The name of the class to receive this field") final JavaType typeName,
@CliOption(key = "notNull", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value cannot be null") final boolean notNull,
@CliOption(key = "nullRequired", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must be null") final boolean nullRequired,
@CliOption(key = "assertFalse", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must assert false") final boolean assertFalse,
@CliOption(key = "assertTrue", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must assert true") final boolean assertTrue,
@CliOption(key = "column", mandatory = false, help = "The JPA @Column name") final String column,
@CliOption(key = "value", mandatory = false, help = "Inserts an optional Spring @Value annotation with the given content") final String value,
@CliOption(key = "comment", mandatory = false, help = "An optional comment for JavaDocs") final String comment,
@CliOption(key = "primitive", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates to use a primitive type") final boolean primitive,
@CliOption(key = "transient", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates to mark the field as transient") final boolean transientModifier,
@CliOption(key = "permitReservedWords", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether reserved words are ignored by Roo") final boolean permitReservedWords) {
final ClassOrInterfaceTypeDetails javaTypeDetails = typeLocationService
.getTypeDetails(typeName);
Validate.notNull(javaTypeDetails,
"The type specified, '%s', doesn't exist", typeName);
final String physicalTypeIdentifier = javaTypeDetails
.getDeclaredByMetadataId();
final BooleanField fieldDetails = new BooleanField(
physicalTypeIdentifier, primitive ? JavaType.BOOLEAN_PRIMITIVE
: JavaType.BOOLEAN_OBJECT, fieldName);
fieldDetails.setNotNull(notNull);
fieldDetails.setNullRequired(nullRequired);
fieldDetails.setAssertFalse(assertFalse);
fieldDetails.setAssertTrue(assertTrue);
if (column != null) {
fieldDetails.setColumn(column);
}
if (comment != null) {
fieldDetails.setComment(comment);
}
if (value != null) {
fieldDetails.setValue(value);
}
insertField(fieldDetails, permitReservedWords, transientModifier);
}
@CliCommand(value = "field date", help = "Adds a private date field to an existing Java source file")
public void addFieldDateJpa(
@CliOption(key = { "", "fieldName" }, mandatory = true, help = "The name of the field to add") final JavaSymbolName fieldName,
@CliOption(key = "type", mandatory = true, optionContext = "java-date", help = "The Java type of the entity") final JavaType fieldType,
@CliOption(key = "persistenceType", mandatory = false, help = "The type of persistent storage to be used") final DateFieldPersistenceType persistenceType,
@CliOption(key = "class", mandatory = false, unspecifiedDefaultValue = "*", optionContext = UPDATE_PROJECT, help = "The name of the class to receive this field") final JavaType typeName,
@CliOption(key = "notNull", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value cannot be null") final boolean notNull,
@CliOption(key = "nullRequired", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must be null") final boolean nullRequired,
@CliOption(key = "future", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must be in the future") final boolean future,
@CliOption(key = "past", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must be in the past") final boolean past,
@CliOption(key = "column", mandatory = false, help = "The JPA @Column name") final String column,
@CliOption(key = "comment", mandatory = false, help = "An optional comment for JavaDocs") final String comment,
@CliOption(key = "value", mandatory = false, help = "Inserts an optional Spring @Value annotation with the given content") final String value,
@CliOption(key = "transient", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates to mark the field as transient") final boolean transientModifier,
@CliOption(key = "permitReservedWords", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether reserved words are ignored by Roo") final boolean permitReservedWords,
@CliOption(key = "dateFormat", mandatory = false, unspecifiedDefaultValue = "MEDIUM", specifiedDefaultValue = "MEDIUM", help = "Indicates the style of the date format (ignored if dateTimeFormatPattern is specified)") final DateTime dateFormat,
@CliOption(key = "timeFormat", mandatory = false, unspecifiedDefaultValue = "NONE", specifiedDefaultValue = "NONE", help = "Indicates the style of the time format (ignored if dateTimeFormatPattern is specified)") final DateTime timeFormat,
@CliOption(key = "dateTimeFormatPattern", mandatory = false, help = "Indicates a DateTime format pattern such as yyyy-MM-dd hh:mm:ss a") final String pattern) {
final ClassOrInterfaceTypeDetails javaTypeDetails = typeLocationService
.getTypeDetails(typeName);
Validate.notNull(javaTypeDetails,
"The type specified, '%s', doesn't exist", typeName);
final String physicalTypeIdentifier = javaTypeDetails
.getDeclaredByMetadataId();
final DateField fieldDetails = new DateField(physicalTypeIdentifier,
fieldType, fieldName);
fieldDetails.setNotNull(notNull);
fieldDetails.setNullRequired(nullRequired);
fieldDetails.setFuture(future);
fieldDetails.setPast(past);
if (JdkJavaType.isDateField(fieldType)) {
fieldDetails
.setPersistenceType(persistenceType != null ? persistenceType
: DateFieldPersistenceType.JPA_TIMESTAMP);
}
if (column != null) {
fieldDetails.setColumn(column);
}
if (comment != null) {
fieldDetails.setComment(comment);
}
if (dateFormat != null) {
fieldDetails.setDateFormat(dateFormat);
}
if (timeFormat != null) {
fieldDetails.setTimeFormat(timeFormat);
}
if (pattern != null) {
fieldDetails.setPattern(pattern);
}
if (value != null) {
fieldDetails.setValue(value);
}
insertField(fieldDetails, permitReservedWords, transientModifier);
}
@CliCommand(value = "field embedded", help = "Adds a private @Embedded field to an existing Java source file ")
public void addFieldEmbeddedJpa(
@CliOption(key = { "", "fieldName" }, mandatory = true, help = "The name of the field to add") final JavaSymbolName fieldName,
@CliOption(key = "type", mandatory = true, optionContext = PROJECT, help = "The Java type of the @Embeddable class") final JavaType fieldType,
@CliOption(key = "class", mandatory = false, unspecifiedDefaultValue = "*", optionContext = UPDATE_PROJECT, help = "The name of the @Entity class to receive this field") final JavaType typeName,
@CliOption(key = "permitReservedWords", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether reserved words are ignored by Roo") final boolean permitReservedWords) {
// Check if the field type is a JPA @Embeddable class
final ClassOrInterfaceTypeDetails cid = typeLocationService
.getTypeDetails(fieldType);
Validate.notNull(
cid,
"The specified target '--type' does not exist or can not be found. Please create this type first.");
Validate.notNull(cid.getAnnotation(EMBEDDABLE),
"The field embedded command is only applicable to JPA @Embeddable field types.");
// Check if the requested entity is a JPA @Entity
final ClassOrInterfaceTypeDetails javaTypeDetails = typeLocationService
.getTypeDetails(typeName);
Validate.notNull(javaTypeDetails,
"The type specified, '%s', doesn't exist", typeName);
final String physicalTypeIdentifier = javaTypeDetails
.getDeclaredByMetadataId();
final PhysicalTypeMetadata targetTypeMetadata = (PhysicalTypeMetadata) metadataService
.get(physicalTypeIdentifier);
Validate.notNull(
targetTypeMetadata,
"The specified target '--class' does not exist or can not be found. Please create this type first.");
final PhysicalTypeDetails targetPtd = targetTypeMetadata
.getMemberHoldingTypeDetails();
Validate.isInstanceOf(MemberHoldingTypeDetails.class, targetPtd);
final ClassOrInterfaceTypeDetails targetTypeCid = (ClassOrInterfaceTypeDetails) targetPtd;
final MemberDetails memberDetails = memberDetailsScanner
.getMemberDetails(this.getClass().getName(), targetTypeCid);
Validate.isTrue(
memberDetails.getAnnotation(ENTITY) != null
|| memberDetails.getAnnotation(PERSISTENT) != null,
"The field embedded command is only applicable to JPA @Entity or Spring Data @Persistent target types.");
final EmbeddedField fieldDetails = new EmbeddedField(
physicalTypeIdentifier, fieldType, fieldName);
insertField(fieldDetails, permitReservedWords, false);
}
@CliCommand(value = "field enum", help = "Adds a private enum field to an existing Java source file")
public void addFieldEnum(
@CliOption(key = { "", "fieldName" }, mandatory = true, help = "The name of the field to add") final JavaSymbolName fieldName,
@CliOption(key = "type", mandatory = true, help = "The enum type of this field") final JavaType fieldType,
@CliOption(key = "class", mandatory = false, unspecifiedDefaultValue = "*", optionContext = UPDATE_PROJECT, help = "The name of the class to receive this field") final JavaType typeName,
@CliOption(key = "column", mandatory = false, help = "The JPA @Column name") final String column,
@CliOption(key = "notNull", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value cannot be null") final boolean notNull,
@CliOption(key = "nullRequired", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must be null") final boolean nullRequired,
@CliOption(key = "enumType", mandatory = false, help = "The fetch semantics at a JPA level") final EnumType enumType,
@CliOption(key = "comment", mandatory = false, help = "An optional comment for JavaDocs") final String comment,
@CliOption(key = "transient", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates to mark the field as transient") final boolean transientModifier,
@CliOption(key = "permitReservedWords", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether reserved words are ignored by Roo") final boolean permitReservedWords) {
final ClassOrInterfaceTypeDetails cid = typeLocationService
.getTypeDetails(typeName);
Validate.notNull(cid, "The type specified, '%s', doesn't exist",
typeName);
final String physicalTypeIdentifier = cid.getDeclaredByMetadataId();
final EnumField fieldDetails = new EnumField(physicalTypeIdentifier,
fieldType, fieldName);
if (column != null) {
fieldDetails.setColumn(column);
}
fieldDetails.setNotNull(notNull);
fieldDetails.setNullRequired(nullRequired);
if (enumType != null) {
fieldDetails.setEnumType(enumType);
}
if (comment != null) {
fieldDetails.setComment(comment);
}
insertField(fieldDetails, permitReservedWords, transientModifier);
}
@CliCommand(value = "field number", help = "Adds a private numeric field to an existing Java source file")
public void addFieldNumber(
@CliOption(key = { "", "fieldName" }, mandatory = true, help = "The name of the field to add") final JavaSymbolName fieldName,
@CliOption(key = "type", mandatory = true, optionContext = "java-number", help = "The Java type of the entity") JavaType fieldType,
@CliOption(key = "class", mandatory = false, unspecifiedDefaultValue = "*", optionContext = UPDATE_PROJECT, help = "The name of the class to receive this field") final JavaType typeName,
@CliOption(key = "notNull", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value cannot be null") final boolean notNull,
@CliOption(key = "nullRequired", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must be null") final boolean nullRequired,
@CliOption(key = "decimalMin", mandatory = false, help = "The BigDecimal string-based representation of the minimum value") final String decimalMin,
@CliOption(key = "decimalMax", mandatory = false, help = "The BigDecimal string based representation of the maximum value") final String decimalMax,
@CliOption(key = "digitsInteger", mandatory = false, help = "Maximum number of integral digits accepted for this number") final Integer digitsInteger,
@CliOption(key = "digitsFraction", mandatory = false, help = "Maximum number of fractional digits accepted for this number") final Integer digitsFraction,
@CliOption(key = "min", mandatory = false, help = "The minimum value") final Long min,
@CliOption(key = "max", mandatory = false, help = "The maximum value") final Long max,
@CliOption(key = "column", mandatory = false, help = "The JPA @Column name") final String column,
@CliOption(key = "comment", mandatory = false, help = "An optional comment for JavaDocs") final String comment,
@CliOption(key = "value", mandatory = false, help = "Inserts an optional Spring @Value annotation with the given content") final String value,
@CliOption(key = "transient", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates to mark the field as transient") final boolean transientModifier,
@CliOption(key = "primitive", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates to use a primitive type if possible") final boolean primitive,
@CliOption(key = "unique", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether to mark the field with a unique constraint") final boolean unique,
@CliOption(key = "permitReservedWords", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether reserved words are ignored by Roo") final boolean permitReservedWords) {
final ClassOrInterfaceTypeDetails javaTypeDetails = typeLocationService
.getTypeDetails(typeName);
Validate.notNull(javaTypeDetails,
"The type specified, '%s', doesn't exist", typeName);
final String physicalTypeIdentifier = javaTypeDetails
.getDeclaredByMetadataId();
if (primitive
&& legalNumericPrimitives.contains(fieldType
.getFullyQualifiedTypeName())) {
fieldType = new JavaType(fieldType.getFullyQualifiedTypeName(), 0,
DataType.PRIMITIVE, null, null);
}
final NumericField fieldDetails = new NumericField(
physicalTypeIdentifier, fieldType, fieldName);
fieldDetails.setNotNull(notNull);
fieldDetails.setNullRequired(nullRequired);
if (decimalMin != null) {
fieldDetails.setDecimalMin(decimalMin);
}
if (decimalMax != null) {
fieldDetails.setDecimalMax(decimalMax);
}
if (digitsInteger != null) {
fieldDetails.setDigitsInteger(digitsInteger);
}
if (digitsFraction != null) {
fieldDetails.setDigitsFraction(digitsFraction);
}
if (min != null) {
fieldDetails.setMin(min);
}
if (max != null) {
fieldDetails.setMax(max);
}
if (column != null) {
fieldDetails.setColumn(column);
}
if (comment != null) {
fieldDetails.setComment(comment);
}
if (unique) {
fieldDetails.setUnique(true);
}
if (value != null) {
fieldDetails.setValue(value);
}
Validate.isTrue(
fieldDetails.isDigitsSetCorrectly(),
"Must specify both --digitsInteger and --digitsFractional for @Digits to be added");
insertField(fieldDetails, permitReservedWords, transientModifier);
}
@CliCommand(value = "field reference", help = "Adds a private reference field to an existing Java source file (eg the 'many' side of a many-to-one)")
public void addFieldReferenceJpa(
@CliOption(key = { "", "fieldName" }, mandatory = true, help = "The name of the field to add") final JavaSymbolName fieldName,
@CliOption(key = "type", mandatory = true, optionContext = PROJECT, help = "The Java type of the entity to reference") final JavaType fieldType,
@CliOption(key = "class", mandatory = false, unspecifiedDefaultValue = "*", optionContext = UPDATE_PROJECT, help = "The name of the class to receive this field") final JavaType typeName,
@CliOption(key = "notNull", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value cannot be null") final boolean notNull,
@CliOption(key = "nullRequired", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must be null") final boolean nullRequired,
@CliOption(key = "joinColumnName", mandatory = false, help = "The JPA @JoinColumn name") final String joinColumnName,
@CliOption(key = "referencedColumnName", mandatory = false, help = "The JPA @JoinColumn referencedColumnName") final String referencedColumnName,
@CliOption(key = "cardinality", mandatory = false, unspecifiedDefaultValue = "MANY_TO_ONE", specifiedDefaultValue = "MANY_TO_ONE", help = "The relationship cardinality at a JPA level") final Cardinality cardinality,
@CliOption(key = "fetch", mandatory = false, help = "The fetch semantics at a JPA level") final Fetch fetch,
@CliOption(key = "comment", mandatory = false, help = "An optional comment for JavaDocs") final String comment,
@CliOption(key = "transient", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates to mark the field as transient") final boolean transientModifier,
@CliOption(key = "permitReservedWords", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether reserved words are ignored by Roo") final boolean permitReservedWords) {
final ClassOrInterfaceTypeDetails cid = typeLocationService
.getTypeDetails(fieldType);
Validate.notNull(
cid,
"The specified target '--type' does not exist or can not be found. Please create this type first.");
// Check if the requested entity is a JPA @Entity
final MemberDetails memberDetails = memberDetailsScanner
.getMemberDetails(this.getClass().getName(), cid);
final AnnotationMetadata entityAnnotation = memberDetails
.getAnnotation(ENTITY);
final AnnotationMetadata persistentAnnotation = memberDetails
.getAnnotation(PERSISTENT);
Validate.isTrue(
entityAnnotation != null || persistentAnnotation != null,
"The field reference command is only applicable to JPA @Entity or Spring Data @Persistent target types.");
Validate.isTrue(cardinality == Cardinality.MANY_TO_ONE
|| cardinality == Cardinality.ONE_TO_ONE,
"Cardinality must be MANY_TO_ONE or ONE_TO_ONE for the field reference command");
final ClassOrInterfaceTypeDetails javaTypeDetails = typeLocationService
.getTypeDetails(typeName);
Validate.notNull(javaTypeDetails,
"The type specified, '%s', doesn't exist", typeName);
final String physicalTypeIdentifier = javaTypeDetails
.getDeclaredByMetadataId();
final ReferenceField fieldDetails = new ReferenceField(
physicalTypeIdentifier, fieldType, fieldName, cardinality);
fieldDetails.setNotNull(notNull);
fieldDetails.setNullRequired(nullRequired);
if (joinColumnName != null) {
fieldDetails.setJoinColumnName(joinColumnName);
}
if (referencedColumnName != null) {
Validate.notNull(joinColumnName,
"@JoinColumn name is required if specifying a referencedColumnName");
fieldDetails.setReferencedColumnName(referencedColumnName);
}
if (fetch != null) {
fieldDetails.setFetch(fetch);
}
if (comment != null) {
fieldDetails.setComment(comment);
}
insertField(fieldDetails, permitReservedWords, transientModifier);
}
@CliCommand(value = "field set", help = "Adds a private Set field to an existing Java source file (eg the 'one' side of a many-to-one)")
public void addFieldSetJpa(
@CliOption(key = { "", "fieldName" }, mandatory = true, help = "The name of the field to add") final JavaSymbolName fieldName,
@CliOption(key = "type", mandatory = true, help = "The entity which will be contained within the Set") final JavaType fieldType,
@CliOption(key = "class", mandatory = false, unspecifiedDefaultValue = "*", optionContext = UPDATE_PROJECT, help = "The name of the class to receive this field") final JavaType typeName,
@CliOption(key = "mappedBy", mandatory = false, help = "The field name on the referenced type which owns the relationship") final JavaSymbolName mappedBy,
@CliOption(key = "notNull", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value cannot be null") final boolean notNull,
@CliOption(key = "nullRequired", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must be null") final boolean nullRequired,
@CliOption(key = "sizeMin", mandatory = false, help = "The minimum number of elements in the collection") final Integer sizeMin,
@CliOption(key = "sizeMax", mandatory = false, help = "The maximum number of elements in the collection") final Integer sizeMax,
@CliOption(key = "cardinality", mandatory = false, unspecifiedDefaultValue = "MANY_TO_MANY", specifiedDefaultValue = "MANY_TO_MANY", help = "The relationship cardinality at a JPA level") Cardinality cardinality,
@CliOption(key = "fetch", mandatory = false, help = "The fetch semantics at a JPA level") final Fetch fetch,
@CliOption(key = "comment", mandatory = false, help = "An optional comment for JavaDocs") final String comment,
@CliOption(key = "transient", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates to mark the field as transient") final boolean transientModifier,
@CliOption(key = "permitReservedWords", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether reserved words are ignored by Roo") final boolean permitReservedWords) {
final ClassOrInterfaceTypeDetails cid = typeLocationService
.getTypeDetails(fieldType);
Validate.notNull(
cid,
"The specified target '--type' does not exist or can not be found. Please create this type first.");
// Check if the requested entity is a JPA @Entity
final MemberDetails memberDetails = memberDetailsScanner
.getMemberDetails(this.getClass().getName(), cid);
final AnnotationMetadata entityAnnotation = memberDetails
.getAnnotation(ENTITY);
final AnnotationMetadata persistentAnnotation = memberDetails
.getAnnotation(PERSISTENT);
if (entityAnnotation != null) {
Validate.isTrue(cardinality == Cardinality.ONE_TO_MANY
|| cardinality == Cardinality.MANY_TO_MANY,
"Cardinality must be ONE_TO_MANY or MANY_TO_MANY for the field set command");
}
else if (cid.getPhysicalTypeCategory() == PhysicalTypeCategory.ENUMERATION) {
cardinality = null;
}
else if (persistentAnnotation != null) {
// Yes, we can deal with that
}
else {
throw new IllegalStateException(
"The field set command is only applicable to enum, JPA @Entity or Spring Data @Persistence elements");
}
final ClassOrInterfaceTypeDetails javaTypeDetails = typeLocationService
.getTypeDetails(typeName);
Validate.notNull(javaTypeDetails,
"The type specified, '%s', doesn't exist", typeName);
final String physicalTypeIdentifier = javaTypeDetails
.getDeclaredByMetadataId();
final SetField fieldDetails = new SetField(physicalTypeIdentifier,
new JavaType(SET.getFullyQualifiedTypeName(), 0, DataType.TYPE,
null, Arrays.asList(fieldType)), fieldName, fieldType,
cardinality);
fieldDetails.setNotNull(notNull);
fieldDetails.setNullRequired(nullRequired);
if (sizeMin != null) {
fieldDetails.setSizeMin(sizeMin);
}
if (sizeMax != null) {
fieldDetails.setSizeMax(sizeMax);
}
if (mappedBy != null) {
fieldDetails.setMappedBy(mappedBy);
}
if (fetch != null) {
fieldDetails.setFetch(fetch);
}
if (comment != null) {
fieldDetails.setComment(comment);
}
insertField(fieldDetails, permitReservedWords, transientModifier);
}
@CliCommand(value = "field list", help = "Adds a private List field to an existing Java source file (eg the 'one' side of a many-to-one)")
public void addFieldListJpa(
@CliOption(key = { "", "fieldName" }, mandatory = true, help = "The name of the field to add") final JavaSymbolName fieldName,
@CliOption(key = "type", mandatory = true, help = "The entity which will be contained within the Set") final JavaType fieldType,
@CliOption(key = "class", mandatory = false, unspecifiedDefaultValue = "*", optionContext = "update,project", help = "The name of the class to receive this field") final JavaType typeName,
@CliOption(key = "mappedBy", mandatory = false, help = "The field name on the referenced type which owns the relationship") final JavaSymbolName mappedBy,
@CliOption(key = "notNull", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value cannot be null") final boolean notNull,
@CliOption(key = "nullRequired", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must be null") final boolean nullRequired,
@CliOption(key = "sizeMin", mandatory = false, help = "The minimum number of elements in the collection") final Integer sizeMin,
@CliOption(key = "sizeMax", mandatory = false, help = "The maximum number of elements in the collection") final Integer sizeMax,
@CliOption(key = "cardinality", mandatory = false, unspecifiedDefaultValue = "MANY_TO_MANY", specifiedDefaultValue = "MANY_TO_MANY", help = "The relationship cardinality at a JPA level") Cardinality cardinality,
@CliOption(key = "fetch", mandatory = false, help = "The fetch semantics at a JPA level") final Fetch fetch,
@CliOption(key = "comment", mandatory = false, help = "An optional comment for JavaDocs") final String comment,
@CliOption(key = "transient", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates to mark the field as transient") final boolean transientModifier,
@CliOption(key = "permitReservedWords", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether reserved words are ignored by Roo") final boolean permitReservedWords) {
final ClassOrInterfaceTypeDetails cid = typeLocationService
.getTypeDetails(fieldType);
Validate.notNull(
cid,
"The specified target '--type' does not exist or can not be found. Please create this type first.");
// Check if the requested entity is a JPA @Entity
final MemberDetails memberDetails = memberDetailsScanner
.getMemberDetails(this.getClass().getName(), cid);
final AnnotationMetadata entityAnnotation = memberDetails
.getAnnotation(ENTITY);
final AnnotationMetadata persistentAnnotation = memberDetails
.getAnnotation(PERSISTENT);
if (entityAnnotation != null) {
Validate.isTrue(cardinality == Cardinality.ONE_TO_MANY
|| cardinality == Cardinality.MANY_TO_MANY,
"Cardinality must be ONE_TO_MANY or MANY_TO_MANY for the field list command");
}
else if (cid.getPhysicalTypeCategory() == PhysicalTypeCategory.ENUMERATION) {
cardinality = null;
}
else if (persistentAnnotation != null) {
// Yes, we can deal with that
}
else {
throw new IllegalStateException(
"The field list command is only applicable to enum, JPA @Entity or Spring Data @Persistence elements");
}
final ClassOrInterfaceTypeDetails javaTypeDetails = typeLocationService
.getTypeDetails(typeName);
Validate.notNull(javaTypeDetails,
"The type specified, '%s' doesn't exist", typeName);
final String physicalTypeIdentifier = javaTypeDetails
.getDeclaredByMetadataId();
final ListField fieldDetails = new ListField(physicalTypeIdentifier,
new JavaType(LIST.getFullyQualifiedTypeName(), 0,
DataType.TYPE, null, Arrays.asList(fieldType)),
fieldName, fieldType, cardinality);
fieldDetails.setNotNull(notNull);
fieldDetails.setNullRequired(nullRequired);
if (sizeMin != null) {
fieldDetails.setSizeMin(sizeMin);
}
if (sizeMax != null) {
fieldDetails.setSizeMax(sizeMax);
}
if (mappedBy != null) {
fieldDetails.setMappedBy(mappedBy);
}
if (fetch != null) {
fieldDetails.setFetch(fetch);
}
if (comment != null) {
fieldDetails.setComment(comment);
}
insertField(fieldDetails, permitReservedWords, transientModifier);
}
@CliCommand(value = "field string", help = "Adds a private string field to an existing Java source file")
public void addFieldString(
@CliOption(key = { "", "fieldName" }, mandatory = true, help = "The name of the field to add") final JavaSymbolName fieldName,
@CliOption(key = "class", mandatory = false, unspecifiedDefaultValue = "*", optionContext = UPDATE_PROJECT, help = "The name of the class to receive this field") final JavaType typeName,
@CliOption(key = "notNull", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value cannot be null") final boolean notNull,
@CliOption(key = "nullRequired", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must be null") final boolean nullRequired,
@CliOption(key = "decimalMin", mandatory = false, help = "The BigDecimal string-based representation of the minimum value") final String decimalMin,
@CliOption(key = "decimalMax", mandatory = false, help = "The BigDecimal string based representation of the maximum value") final String decimalMax,
@CliOption(key = "sizeMin", mandatory = false, help = "The minimum string length") final Integer sizeMin,
@CliOption(key = "sizeMax", mandatory = false, help = "The maximum string length") final Integer sizeMax,
@CliOption(key = "regexp", mandatory = false, help = "The required regular expression pattern") final String regexp,
@CliOption(key = "column", mandatory = false, help = "The JPA @Column name") final String column,
@CliOption(key = "value", mandatory = false, help = "Inserts an optional Spring @Value annotation with the given content") final String value,
@CliOption(key = "comment", mandatory = false, help = "An optional comment for JavaDocs") final String comment,
@CliOption(key = "transient", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates to mark the field as transient") final boolean transientModifier,
@CliOption(key = "unique", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether to mark the field with a unique constraint") final boolean unique,
@CliOption(key = "permitReservedWords", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether reserved words are ignored by Roo") final boolean permitReservedWords,
@CliOption(key = "lob", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates that this field is a Large Object") final boolean lob) {
final ClassOrInterfaceTypeDetails cid = typeLocationService
.getTypeDetails(typeName);
Validate.notNull(cid, "The type specified, '%s', doesn't exist",
typeName);
final String physicalTypeIdentifier = cid.getDeclaredByMetadataId();
final StringField fieldDetails = new StringField(
physicalTypeIdentifier, fieldName);
fieldDetails.setNotNull(notNull);
fieldDetails.setNullRequired(nullRequired);
if (decimalMin != null) {
fieldDetails.setDecimalMin(decimalMin);
}
if (decimalMax != null) {
fieldDetails.setDecimalMax(decimalMax);
}
if (sizeMin != null) {
fieldDetails.setSizeMin(sizeMin);
}
if (sizeMax != null) {
fieldDetails.setSizeMax(sizeMax);
}
if (regexp != null) {
fieldDetails.setRegexp(regexp.replace("\\", "\\\\"));
}
if (column != null) {
fieldDetails.setColumn(column);
}
if (comment != null) {
fieldDetails.setComment(comment);
}
if (unique) {
fieldDetails.setUnique(true);
}
if (value != null) {
fieldDetails.setValue(value);
}
if (lob) {
fieldDetails.getInitedAnnotations().add(
new AnnotationMetadataBuilder("javax.persistence.Lob"));
}
insertField(fieldDetails, permitReservedWords, transientModifier);
}
@CliCommand(value = "field file", help = "Adds a byte array field for storing uploaded file contents (JSF-scaffolded UIs only)")
public void addFileUploadField(
@CliOption(key = { "", "fieldName" }, mandatory = true, help = "The name of the file upload field to add") final JavaSymbolName fieldName,
@CliOption(key = "class", mandatory = false, unspecifiedDefaultValue = "*", optionContext = UPDATE_PROJECT, help = "The name of the class to receive this field") final JavaType typeName,
@CliOption(key = "contentType", mandatory = true, help = "The content type of the file") final UploadedFileContentType contentType,
@CliOption(key = "autoUpload", mandatory = false, specifiedDefaultValue = "true", unspecifiedDefaultValue = "false", help = "Whether the file is uploaded automatically when selected") final boolean autoUpload,
@CliOption(key = "column", mandatory = false, help = "The JPA @Column name") final String column,
@CliOption(key = "notNull", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value cannot be null") final boolean notNull,
@CliOption(key = "permitReservedWords", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether reserved words are ignored by Roo") final boolean permitReservedWords) {
final ClassOrInterfaceTypeDetails cid = typeLocationService
.getTypeDetails(typeName);
Validate.notNull(cid, "The type specified, '%s', doesn't exist",
typeName);
final String physicalTypeIdentifier = cid.getDeclaredByMetadataId();
final UploadedFileField fieldDetails = new UploadedFileField(
physicalTypeIdentifier, fieldName, contentType);
fieldDetails.setAutoUpload(autoUpload);
fieldDetails.setNotNull(notNull);
if (column != null) {
fieldDetails.setColumn(column);
}
insertField(fieldDetails, permitReservedWords, false);
}
protected void deactivate(final ComponentContext context) {
staticFieldConverter.remove(Cardinality.class);
staticFieldConverter.remove(Fetch.class);
staticFieldConverter.remove(EnumType.class);
staticFieldConverter.remove(DateTime.class);
}
private void insertField(final FieldDetails fieldDetails,
final boolean permitReservedWords, final boolean transientModifier) {
if (!permitReservedWords) {
ReservedWords.verifyReservedWordsNotPresent(fieldDetails
.getFieldName());
if (fieldDetails.getColumn() != null) {
ReservedWords.verifyReservedWordsNotPresent(fieldDetails
.getColumn());
}
}
final List<AnnotationMetadataBuilder> annotations = fieldDetails
.getInitedAnnotations();
fieldDetails.decorateAnnotationsList(annotations);
fieldDetails.setAnnotations(annotations);
String initializer = null;
if (fieldDetails instanceof CollectionField) {
final CollectionField collectionField = (CollectionField) fieldDetails;
initializer = "new " + collectionField.getInitializer() + "()";
}
else if (fieldDetails instanceof DateField
&& fieldDetails.getFieldName().getSymbolName()
.equals("created")) {
initializer = "new Date()";
}
int modifier = Modifier.PRIVATE;
if (transientModifier) {
modifier += Modifier.TRANSIENT;
}
fieldDetails.setModifiers(modifier);
// Format the passed-in comment (if given)
formatFieldComment(fieldDetails);
final FieldMetadataBuilder fieldBuilder = new FieldMetadataBuilder(
fieldDetails);
fieldBuilder.setFieldInitializer(initializer);
typeManagementService.addField(fieldBuilder.build());
}
private void formatFieldComment(FieldDetails fieldDetails) {
// If a comment was defined, we need to format it
if (fieldDetails.getComment() != null) {
// First replace all "" with the proper escape sequence \"
String unescapedMultiLineComment = fieldDetails.getComment()
.replaceAll("\"\"", "\\\\\"");
// Then unescape all characters
unescapedMultiLineComment = StringEscapeUtils
.unescapeJava(unescapedMultiLineComment);
CommentFormatter commentFormatter = new CommentFormatter();
String javadocComment = commentFormatter
.formatStringAsJavadoc(unescapedMultiLineComment);
fieldDetails.setComment(commentFormatter.format(javadocComment, 1));
}
}
@CliCommand(value = "field other", help = "Inserts a private field into the specified file")
public void insertField(
@CliOption(key = "fieldName", mandatory = true, help = "The name of the field") final JavaSymbolName fieldName,
@CliOption(key = "type", mandatory = true, help = "The Java type of this field") final JavaType fieldType,
@CliOption(key = "class", mandatory = false, unspecifiedDefaultValue = "*", optionContext = UPDATE_PROJECT, help = "The name of the class to receive this field") final JavaType typeName,
@CliOption(key = "notNull", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value cannot be null") final boolean notNull,
@CliOption(key = "nullRequired", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must be null") final boolean nullRequired,
@CliOption(key = "comment", mandatory = false, help = "An optional comment for JavaDocs") final String comment,
@CliOption(key = "column", mandatory = false, help = "The JPA @Column name") final String column,
@CliOption(key = "value", mandatory = false, help = "Inserts an optional Spring @Value annotation with the given content") final String value,
@CliOption(key = "transient", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates to mark the field as transient") final boolean transientModifier,
@CliOption(key = "permitReservedWords", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether reserved words are ignored by Roo") final boolean permitReservedWords) {
final ClassOrInterfaceTypeDetails cid = typeLocationService
.getTypeDetails(typeName);
Validate.notNull(cid, "The type specified, '%s', doesn't exist",
typeName);
final String physicalTypeIdentifier = cid.getDeclaredByMetadataId();
final FieldDetails fieldDetails = new FieldDetails(
physicalTypeIdentifier, fieldType, fieldName);
fieldDetails.setNotNull(notNull);
fieldDetails.setNullRequired(nullRequired);
if (comment != null) {
fieldDetails.setComment(comment);
}
if (column != null) {
fieldDetails.setColumn(column);
}
insertField(fieldDetails, permitReservedWords, transientModifier);
}
@CliAvailabilityIndicator({ "field other", "field number", "field string",
"field date", "field boolean", "field enum", "field embedded",
"field file" })
public boolean isJdkFieldManagementAvailable() {
return projectOperations.isFocusedProjectAvailable();
}
@CliAvailabilityIndicator({ "field reference", "field set", "field list" })
public boolean isJpaFieldManagementAvailable() {
// In a separate method in case we decide to check for JPA registration
// in the future
return projectOperations.isFocusedProjectAvailable();
}
}