package org.springframework.roo.addon.test;
import static org.springframework.roo.classpath.customdata.CustomDataKeys.COUNT_ALL_METHOD;
import static org.springframework.roo.classpath.customdata.CustomDataKeys.FIND_ALL_METHOD;
import static org.springframework.roo.classpath.customdata.CustomDataKeys.FIND_ENTRIES_METHOD;
import static org.springframework.roo.classpath.customdata.CustomDataKeys.FIND_METHOD;
import static org.springframework.roo.classpath.customdata.CustomDataKeys.FLUSH_METHOD;
import static org.springframework.roo.classpath.customdata.CustomDataKeys.IDENTIFIER_ACCESSOR_METHOD;
import static org.springframework.roo.classpath.customdata.CustomDataKeys.MERGE_METHOD;
import static org.springframework.roo.classpath.customdata.CustomDataKeys.PERSISTENT_TYPE;
import static org.springframework.roo.classpath.customdata.CustomDataKeys.PERSIST_METHOD;
import static org.springframework.roo.classpath.customdata.CustomDataKeys.REMOVE_METHOD;
import static org.springframework.roo.model.JavaType.INT_PRIMITIVE;
import static org.springframework.roo.model.RooJavaType.ROO_DATA_ON_DEMAND;
import static org.springframework.roo.model.RooJavaType.ROO_INTEGRATION_TEST;
import static org.springframework.roo.model.RooJavaType.ROO_JPA_ACTIVE_RECORD;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
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.addon.configurable.ConfigurableMetadataProvider;
import org.springframework.roo.addon.dod.DataOnDemandMetadata;
import org.springframework.roo.classpath.PhysicalTypeDetails;
import org.springframework.roo.classpath.PhysicalTypeIdentifier;
import org.springframework.roo.classpath.PhysicalTypeIdentifierNamingUtils;
import org.springframework.roo.classpath.PhysicalTypeMetadata;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.MemberFindingUtils;
import org.springframework.roo.classpath.details.MemberHoldingTypeDetails;
import org.springframework.roo.classpath.details.MethodMetadata;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.details.annotations.StringAttributeValue;
import org.springframework.roo.classpath.itd.AbstractItdMetadataProvider;
import org.springframework.roo.classpath.itd.ItdTypeDetailsProvidingMetadataItem;
import org.springframework.roo.classpath.layers.LayerService;
import org.springframework.roo.classpath.layers.LayerType;
import org.springframework.roo.classpath.layers.MemberTypeAdditions;
import org.springframework.roo.classpath.layers.MethodParameter;
import org.springframework.roo.classpath.scanner.MemberDetails;
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.ProjectMetadata;
import org.springframework.roo.project.ProjectOperations;
/**
* Implementation of {@link IntegrationTestMetadataProvider}.
*
* @author Ben Alex
* @since 1.0
*/
@Component(immediate = true)
@Service
public class IntegrationTestMetadataProviderImpl extends
AbstractItdMetadataProvider implements IntegrationTestMetadataProvider {
private static final int LAYER_POSITION = LayerType.HIGHEST.getPosition();
private static final JavaSymbolName TRANSACTION_MANAGER_ATTRIBUTE = new JavaSymbolName(
"transactionManager");
@Reference private ConfigurableMetadataProvider configurableMetadataProvider;
@Reference private LayerService layerService;
@Reference private ProjectOperations projectOperations;
private final Map<JavaType, String> managedEntityTypes = new HashMap<JavaType, String>();
private final Set<String> producedMids = new LinkedHashSet<String>();
private Boolean wasGaeEnabled;
protected void activate(final ComponentContext context) {
metadataDependencyRegistry.addNotificationListener(this);
metadataDependencyRegistry.registerDependency(
PhysicalTypeIdentifier.getMetadataIdentiferType(),
getProvidesType());
// Integration test classes are @Configurable because they may need DI
// of other DOD classes that provide M:1 relationships
configurableMetadataProvider.addMetadataTrigger(ROO_INTEGRATION_TEST);
addMetadataTrigger(ROO_INTEGRATION_TEST);
}
@Override
protected String createLocalIdentifier(final JavaType javaType,
final LogicalPath path) {
return IntegrationTestMetadata.createIdentifier(javaType, path);
}
protected void deactivate(final ComponentContext context) {
metadataDependencyRegistry.removeNotificationListener(this);
metadataDependencyRegistry.deregisterDependency(
PhysicalTypeIdentifier.getMetadataIdentiferType(),
getProvidesType());
configurableMetadataProvider
.removeMetadataTrigger(ROO_INTEGRATION_TEST);
removeMetadataTrigger(ROO_INTEGRATION_TEST);
}
/**
* Returns the {@link JavaType} for the given entity's "data on demand"
* class.
*
* @param entity the entity for which to get the DoD type
* @return a non-<code>null</code> type (which may or may not exist yet)
*/
private JavaType getDataOnDemandType(final JavaType entity) {
// First check for an existing type with the standard DoD naming
// convention
final JavaType defaultDodType = new JavaType(
entity.getFullyQualifiedTypeName() + "DataOnDemand");
if (typeLocationService.getTypeDetails(defaultDodType) != null) {
return defaultDodType;
}
// Otherwise we look through all DoD-annotated classes for this entity's
// one
for (final ClassOrInterfaceTypeDetails dodType : typeLocationService
.findClassesOrInterfaceDetailsWithAnnotation(ROO_DATA_ON_DEMAND)) {
final AnnotationMetadata dodAnnotation = MemberFindingUtils
.getFirstAnnotation(dodType, ROO_DATA_ON_DEMAND);
if (dodAnnotation != null
&& dodAnnotation.getAttribute("entity").getValue()
.equals(entity)) {
return dodType.getName();
}
}
// No existing DoD class was found for this entity, so use the default
// name
return defaultDodType;
}
private ClassOrInterfaceTypeDetails getEntitySuperclass(
final JavaType entity) {
final String physicalTypeIdentifier = PhysicalTypeIdentifier
.createIdentifier(entity,
typeLocationService.getTypePath(entity));
final PhysicalTypeMetadata ptm = (PhysicalTypeMetadata) metadataService
.get(physicalTypeIdentifier);
Validate.notNull(ptm, "Java source code unavailable for type %s",
PhysicalTypeIdentifier.getFriendlyName(physicalTypeIdentifier));
final PhysicalTypeDetails ptd = ptm.getMemberHoldingTypeDetails();
Validate.notNull(ptd,
"Java source code details unavailable for type %s",
PhysicalTypeIdentifier.getFriendlyName(physicalTypeIdentifier));
Validate.isInstanceOf(ClassOrInterfaceTypeDetails.class, ptd,
"Java source code is immutable for type %s",
PhysicalTypeIdentifier.getFriendlyName(physicalTypeIdentifier));
final ClassOrInterfaceTypeDetails cid = (ClassOrInterfaceTypeDetails) ptd;
return cid.getSuperclass();
}
@Override
protected String getGovernorPhysicalTypeIdentifier(
final String metadataIdentificationString) {
final JavaType javaType = IntegrationTestMetadata
.getJavaType(metadataIdentificationString);
final LogicalPath path = IntegrationTestMetadata
.getPath(metadataIdentificationString);
return PhysicalTypeIdentifier.createIdentifier(javaType, path);
}
public String getItdUniquenessFilenameSuffix() {
return "IntegrationTest";
}
@Override
protected ItdTypeDetailsProvidingMetadataItem getMetadata(
final String metadataIdentificationString,
final JavaType aspectName,
final PhysicalTypeMetadata governorPhysicalTypeMetadata,
final String itdFilename) {
// We need to parse the annotation, which we expect to be present
final IntegrationTestAnnotationValues annotationValues = new IntegrationTestAnnotationValues(
governorPhysicalTypeMetadata);
final JavaType entity = annotationValues.getEntity();
if (!annotationValues.isAnnotationFound() || entity == null) {
return null;
}
final JavaType dataOnDemandType = getDataOnDemandType(entity);
final String dataOnDemandMetadataKey = DataOnDemandMetadata
.createIdentifier(dataOnDemandType,
typeLocationService.getTypePath(dataOnDemandType));
final DataOnDemandMetadata dataOnDemandMetadata = (DataOnDemandMetadata) metadataService
.get(dataOnDemandMetadataKey);
// We need to be informed if our dependent metadata changes
metadataDependencyRegistry.registerDependency(dataOnDemandMetadataKey,
metadataIdentificationString);
if (dataOnDemandMetadata == null || !dataOnDemandMetadata.isValid()) {
return null;
}
final JavaType identifierType = persistenceMemberLocator
.getIdentifierType(entity);
if (identifierType == null) {
return null;
}
final MemberDetails memberDetails = getMemberDetails(entity);
if (memberDetails == null) {
return null;
}
final MemberHoldingTypeDetails persistenceMemberHoldingTypeDetails = MemberFindingUtils
.getMostConcreteMemberHoldingTypeDetailsWithTag(memberDetails,
PERSISTENT_TYPE);
if (persistenceMemberHoldingTypeDetails == null) {
return null;
}
// We need to be informed if our dependent metadata changes
metadataDependencyRegistry.registerDependency(
persistenceMemberHoldingTypeDetails.getDeclaredByMetadataId(),
metadataIdentificationString);
final MethodParameter firstResultParameter = new MethodParameter(
INT_PRIMITIVE, "firstResult");
final MethodParameter maxResultsParameter = new MethodParameter(
INT_PRIMITIVE, "maxResults");
final MethodMetadata identifierAccessorMethod = memberDetails
.getMostConcreteMethodWithTag(IDENTIFIER_ACCESSOR_METHOD);
final MethodMetadata versionAccessorMethod = persistenceMemberLocator
.getVersionAccessor(entity);
final MemberTypeAdditions countMethodAdditions = layerService
.getMemberTypeAdditions(metadataIdentificationString,
COUNT_ALL_METHOD.name(), entity, identifierType,
LAYER_POSITION);
final MemberTypeAdditions findMethodAdditions = layerService
.getMemberTypeAdditions(metadataIdentificationString,
FIND_METHOD.name(), entity, identifierType,
LAYER_POSITION, new MethodParameter(identifierType,
"id"));
final MemberTypeAdditions findAllMethodAdditions = layerService
.getMemberTypeAdditions(metadataIdentificationString,
FIND_ALL_METHOD.name(), entity, identifierType,
LAYER_POSITION);
final MemberTypeAdditions findEntriesMethod = layerService
.getMemberTypeAdditions(metadataIdentificationString,
FIND_ENTRIES_METHOD.name(), entity, identifierType,
LAYER_POSITION, firstResultParameter,
maxResultsParameter);
final MethodParameter entityParameter = new MethodParameter(entity,
"obj");
final MemberTypeAdditions flushMethodAdditions = layerService
.getMemberTypeAdditions(metadataIdentificationString,
FLUSH_METHOD.name(), entity, identifierType,
LAYER_POSITION, entityParameter);
final MemberTypeAdditions mergeMethodAdditions = layerService
.getMemberTypeAdditions(metadataIdentificationString,
MERGE_METHOD.name(), entity, identifierType,
LAYER_POSITION, entityParameter);
final MemberTypeAdditions persistMethodAdditions = layerService
.getMemberTypeAdditions(metadataIdentificationString,
PERSIST_METHOD.name(), entity, identifierType,
LAYER_POSITION, entityParameter);
final MemberTypeAdditions removeMethodAdditions = layerService
.getMemberTypeAdditions(metadataIdentificationString,
REMOVE_METHOD.name(), entity, identifierType,
LAYER_POSITION, entityParameter);
if (persistMethodAdditions == null || findMethodAdditions == null
|| identifierAccessorMethod == null) {
return null;
}
String transactionManager = null;
final AnnotationMetadata jpaActiveRecordAnnotation = memberDetails
.getAnnotation(ROO_JPA_ACTIVE_RECORD);
if (jpaActiveRecordAnnotation != null) {
final StringAttributeValue transactionManagerAttr = (StringAttributeValue) jpaActiveRecordAnnotation
.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE);
if (transactionManagerAttr != null) {
transactionManager = transactionManagerAttr.getValue();
}
}
final boolean hasEmbeddedIdentifier = dataOnDemandMetadata
.hasEmbeddedIdentifier();
final boolean entityHasSuperclass = getEntitySuperclass(entity) != null;
// In order to handle switching between GAE and JPA produced MIDs need
// to be remembered so they can be regenerated on JPA <-> GAE switch
producedMids.add(metadataIdentificationString);
// Maintain a list of entities that are being tested
managedEntityTypes.put(entity, metadataIdentificationString);
final String moduleName = PhysicalTypeIdentifierNamingUtils.getPath(
metadataIdentificationString).getModule();
final boolean isGaeEnabled = projectOperations
.isProjectAvailable(moduleName)
&& projectOperations.isFeatureInstalledInModule(
FeatureNames.GAE, moduleName);
return new IntegrationTestMetadata(metadataIdentificationString,
aspectName, governorPhysicalTypeMetadata, annotationValues,
dataOnDemandMetadata, identifierAccessorMethod,
versionAccessorMethod, countMethodAdditions,
findMethodAdditions, findAllMethodAdditions, findEntriesMethod,
flushMethodAdditions, mergeMethodAdditions,
persistMethodAdditions, removeMethodAdditions,
transactionManager, hasEmbeddedIdentifier, entityHasSuperclass,
isGaeEnabled);
}
public String getProvidesType() {
return IntegrationTestMetadata.getMetadataIdentiferType();
}
private void handleChangesToLayeringForTestedEntities(
final JavaType physicalType) {
final MemberHoldingTypeDetails memberHoldingTypeDetails = typeLocationService
.getTypeDetails(physicalType);
if (memberHoldingTypeDetails != null) {
for (final JavaType type : memberHoldingTypeDetails
.getLayerEntities()) {
handleChangesToTestedEntities(type);
}
}
}
private void handleChangesToTestedEntities(final JavaType physicalType) {
final String localMid = managedEntityTypes.get(physicalType);
if (localMid != null) {
// One of the entities for which we produce metadata has changed;
// refresh that metadata
metadataService.get(localMid);
}
}
/**
* Handles a generic change (i.e. with no explicit downstream dependency) to
* the given physical type
*
* @param physicalType the type that changed (required)
*/
private void handleGenericChangeToPhysicalType(final JavaType physicalType) {
handleChangesToTestedEntities(physicalType);
handleChangesToLayeringForTestedEntities(physicalType);
}
/**
* Handles a generic change (i.e. with no explicit downstream dependency) to
* the project metadata
*/
private void handleGenericChangeToProject(final String moduleName) {
final ProjectMetadata projectMetadata = projectOperations
.getProjectMetadata(moduleName);
if (projectMetadata != null && projectMetadata.isValid()) {
final boolean isGaeEnabled = projectOperations
.isFeatureInstalledInModule(FeatureNames.GAE, moduleName);
// We need to determine if the persistence state has changed, we do
// this by comparing the last known state to the current state
final boolean hasGaeStateChanged = wasGaeEnabled == null
|| isGaeEnabled != wasGaeEnabled;
if (hasGaeStateChanged) {
wasGaeEnabled = isGaeEnabled;
for (final String producedMid : producedMids) {
metadataService.evictAndGet(producedMid);
}
}
}
}
@Override
protected void notifyForGenericListener(final String upstreamDependency) {
if (PhysicalTypeIdentifier.isValid(upstreamDependency)) {
handleGenericChangeToPhysicalType(PhysicalTypeIdentifier
.getJavaType(upstreamDependency));
}
if (ProjectMetadata.isValid(upstreamDependency)) {
handleGenericChangeToProject(ProjectMetadata
.getModuleName(upstreamDependency));
}
}
}