package org.springframework.roo.addon.finder;
import static org.springframework.roo.model.RooJavaType.ROO_JPA_ACTIVE_RECORD;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Logger;
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.springframework.roo.addon.jpa.activerecord.JpaActiveRecordMetadata;
import org.springframework.roo.classpath.PhysicalTypeIdentifier;
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.ClassOrInterfaceTypeDetailsBuilder;
import org.springframework.roo.classpath.details.FieldMetadata;
import org.springframework.roo.classpath.details.MemberFindingUtils;
import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
import org.springframework.roo.classpath.details.annotations.ArrayAttributeValue;
import org.springframework.roo.classpath.details.annotations.StringAttributeValue;
import org.springframework.roo.classpath.persistence.PersistenceMemberLocator;
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.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.project.FeatureNames;
import org.springframework.roo.project.LogicalPath;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.support.logging.HandlerUtils;
/**
* Implementation of {@link FinderOperations}.
*
* @author Stefan Schmidt
* @since 1.0
*/
@Component
@Service
public class FinderOperationsImpl implements FinderOperations {
private static final Logger LOGGER = HandlerUtils
.getLogger(FinderOperationsImpl.class);
@Reference private DynamicFinderServices dynamicFinderServices;
@Reference private MemberDetailsScanner memberDetailsScanner;
@Reference private MetadataService metadataService;
@Reference private PersistenceMemberLocator persistenceMemberLocator;
@Reference private ProjectOperations projectOperations;
@Reference private TypeLocationService typeLocationService;
@Reference private TypeManagementService typeManagementService;
private String getErrorMsg() {
return "Annotation " + ROO_JPA_ACTIVE_RECORD.getSimpleTypeName()
+ " attribute 'finders' must be an array of strings";
}
public void installFinder(final JavaType typeName,
final JavaSymbolName finderName) {
Validate.notNull(typeName, "Java type required");
Validate.notNull(finderName, "Finer name required");
final String id = typeLocationService
.getPhysicalTypeIdentifier(typeName);
if (id == null) {
LOGGER.warning("Cannot locate source for '"
+ typeName.getFullyQualifiedTypeName() + "'");
return;
}
// Go and get the entity metadata, as any type with finders has to be an
// entity
final JavaType javaType = PhysicalTypeIdentifier.getJavaType(id);
final LogicalPath path = PhysicalTypeIdentifier.getPath(id);
final String entityMid = JpaActiveRecordMetadata.createIdentifier(
javaType, path);
// Get the entity metadata
final JpaActiveRecordMetadata jpaActiveRecordMetadata = (JpaActiveRecordMetadata) metadataService
.get(entityMid);
if (jpaActiveRecordMetadata == null) {
LOGGER.warning("Cannot provide finders because '"
+ typeName.getFullyQualifiedTypeName()
+ "' is not an entity - " + entityMid);
return;
}
// We know the file exists, as there's already entity metadata for it
final ClassOrInterfaceTypeDetails cid = typeLocationService
.getTypeDetails(id);
if (cid == null) {
throw new IllegalArgumentException("Cannot locate source for '"
+ javaType.getFullyQualifiedTypeName() + "'");
}
// We know there should be an existing RooEntity annotation
final List<? extends AnnotationMetadata> annotations = cid
.getAnnotations();
final AnnotationMetadata jpaActiveRecordAnnotation = MemberFindingUtils
.getAnnotationOfType(annotations, ROO_JPA_ACTIVE_RECORD);
if (jpaActiveRecordAnnotation == null) {
LOGGER.warning("Unable to find the entity annotation on '"
+ typeName.getFullyQualifiedTypeName() + "'");
return;
}
// Confirm they typed a valid finder name
final MemberDetails memberDetails = memberDetailsScanner
.getMemberDetails(getClass().getName(), cid);
if (dynamicFinderServices.getQueryHolder(memberDetails, finderName,
jpaActiveRecordMetadata.getPlural(),
jpaActiveRecordMetadata.getEntityName()) == null) {
LOGGER.warning("Finder name '" + finderName.getSymbolName()
+ "' either does not exist or contains an error");
return;
}
// Make a destination list to store our final attributes
final List<AnnotationAttributeValue<?>> attributes = new ArrayList<AnnotationAttributeValue<?>>();
final List<StringAttributeValue> desiredFinders = new ArrayList<StringAttributeValue>();
// Copy the existing attributes, excluding the "finder" attribute
boolean alreadyAdded = false;
final AnnotationAttributeValue<?> val = jpaActiveRecordAnnotation
.getAttribute(new JavaSymbolName("finders"));
if (val != null) {
// Ensure we have an array of strings
if (!(val instanceof ArrayAttributeValue<?>)) {
LOGGER.warning(getErrorMsg());
return;
}
final ArrayAttributeValue<?> arrayVal = (ArrayAttributeValue<?>) val;
for (final Object o : arrayVal.getValue()) {
if (!(o instanceof StringAttributeValue)) {
LOGGER.warning(getErrorMsg());
return;
}
final StringAttributeValue sv = (StringAttributeValue) o;
if (sv.getValue().equals(finderName.getSymbolName())) {
alreadyAdded = true;
}
desiredFinders.add(sv);
}
}
// Add the desired finder to the end
if (!alreadyAdded) {
desiredFinders.add(new StringAttributeValue(new JavaSymbolName(
"ignored"), finderName.getSymbolName()));
}
// Now let's add the "finders" attribute
attributes.add(new ArrayAttributeValue<StringAttributeValue>(
new JavaSymbolName("finders"), desiredFinders));
final ClassOrInterfaceTypeDetailsBuilder cidBuilder = new ClassOrInterfaceTypeDetailsBuilder(
cid);
final AnnotationMetadataBuilder annotation = new AnnotationMetadataBuilder(
ROO_JPA_ACTIVE_RECORD, attributes);
cidBuilder.updateTypeAnnotation(annotation.build(),
new HashSet<JavaSymbolName>());
typeManagementService.createOrUpdateTypeOnDisk(cidBuilder.build());
}
public boolean isFinderInstallationPossible() {
return projectOperations.isFocusedProjectAvailable()
&& projectOperations
.isFeatureInstalledInFocusedModule(FeatureNames.JPA);
}
public SortedSet<String> listFindersFor(final JavaType typeName,
final Integer depth) {
Validate.notNull(typeName, "Java type required");
final String id = typeLocationService
.getPhysicalTypeIdentifier(typeName);
if (id == null) {
throw new IllegalArgumentException("Cannot locate source for '"
+ typeName.getFullyQualifiedTypeName() + "'");
}
// Go and get the entity metadata, as any type with finders has to be an
// entity
final JavaType javaType = PhysicalTypeIdentifier.getJavaType(id);
final LogicalPath path = PhysicalTypeIdentifier.getPath(id);
final String entityMid = JpaActiveRecordMetadata.createIdentifier(
javaType, path);
// Get the entity metadata
final JpaActiveRecordMetadata jpaActiveRecordMetadata = (JpaActiveRecordMetadata) metadataService
.get(entityMid);
if (jpaActiveRecordMetadata == null) {
throw new IllegalArgumentException(
"Cannot provide finders because '"
+ typeName.getFullyQualifiedTypeName()
+ "' is not an 'active record' entity");
}
// Get the member details
final PhysicalTypeMetadata physicalTypeMetadata = (PhysicalTypeMetadata) metadataService
.get(PhysicalTypeIdentifier.createIdentifier(javaType, path));
if (physicalTypeMetadata == null) {
throw new IllegalStateException(
"Could not determine physical type metadata for type "
+ javaType);
}
final ClassOrInterfaceTypeDetails cid = physicalTypeMetadata
.getMemberHoldingTypeDetails();
if (cid == null) {
throw new IllegalStateException(
"Could not determine class or interface type details for type "
+ javaType);
}
final MemberDetails memberDetails = memberDetailsScanner
.getMemberDetails(getClass().getName(), cid);
final List<FieldMetadata> idFields = persistenceMemberLocator
.getIdentifierFields(javaType);
final FieldMetadata versionField = persistenceMemberLocator
.getVersionField(javaType);
// Compute the finders (excluding the ID, version, and EM fields)
final Set<JavaSymbolName> exclusions = new HashSet<JavaSymbolName>();
exclusions.add(jpaActiveRecordMetadata.getEntityManagerField()
.getFieldName());
for (final FieldMetadata idField : idFields) {
exclusions.add(idField.getFieldName());
}
if (versionField != null) {
exclusions.add(versionField.getFieldName());
}
final SortedSet<String> result = new TreeSet<String>();
final List<JavaSymbolName> finders = dynamicFinderServices.getFinders(
memberDetails, jpaActiveRecordMetadata.getPlural(), depth,
exclusions);
for (final JavaSymbolName finder : finders) {
// Avoid displaying problematic finders
try {
final QueryHolder queryHolder = dynamicFinderServices
.getQueryHolder(memberDetails, finder,
jpaActiveRecordMetadata.getPlural(),
jpaActiveRecordMetadata.getEntityName());
final List<JavaSymbolName> parameterNames = queryHolder
.getParameterNames();
final List<JavaType> parameterTypes = queryHolder
.getParameterTypes();
final StringBuilder signature = new StringBuilder();
int x = -1;
for (final JavaType param : parameterTypes) {
x++;
if (x > 0) {
signature.append(", ");
}
signature.append(param.getSimpleTypeName()).append(" ")
.append(parameterNames.get(x).getSymbolName());
}
result.add(finder.getSymbolName() + "(" + signature + ")" /*
* query:
* '"
* +
* query
* +
* "'"
*/);
}
catch (final RuntimeException e) {
result.add(finder.getSymbolName() + " - failure");
}
}
return result;
}
}