package org.springframework.roo.addon.gwt.locator;
import static org.springframework.roo.addon.gwt.GwtJavaType.LOCATOR;
import static org.springframework.roo.model.JavaType.CLASS;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import org.apache.commons.lang3.StringUtils;
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.gwt.GwtTemplateService;
import org.springframework.roo.addon.gwt.GwtTypeService;
import org.springframework.roo.addon.gwt.GwtUtils;
import org.springframework.roo.classpath.PhysicalTypeCategory;
import org.springframework.roo.classpath.PhysicalTypeIdentifier;
import org.springframework.roo.classpath.TypeLocationService;
import org.springframework.roo.classpath.TypeManagementService;
import org.springframework.roo.classpath.customdata.CustomDataKeys;
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.MethodMetadata;
import org.springframework.roo.classpath.details.MethodMetadataBuilder;
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.itd.InvocableMemberBodyBuilder;
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.persistence.PersistenceMemberLocator;
import org.springframework.roo.metadata.MetadataDependencyRegistry;
import org.springframework.roo.metadata.MetadataIdentificationUtils;
import org.springframework.roo.metadata.MetadataItem;
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.RooJavaType;
import org.springframework.roo.model.SpringJavaType;
import org.springframework.roo.project.LogicalPath;
import org.springframework.roo.project.ProjectOperations;
@Component(immediate = true)
@Service
public class GwtLocatorMetadataProviderImpl implements
GwtLocatorMetadataProvider {
private static final int LAYER_POSITION = LayerType.HIGHEST.getPosition();
@Reference GwtTypeService gwtTypeService;
@Reference GwtTemplateService gwtTemplateService;
@Reference LayerService layerService;
@Reference MetadataDependencyRegistry metadataDependencyRegistry;
@Reference MetadataService metadataService;
@Reference PersistenceMemberLocator persistenceMemberLocator;
@Reference ProjectOperations projectOperations;
@Reference TypeLocationService typeLocationService;
@Reference TypeManagementService typeManagementService;
protected void activate(final ComponentContext context) {
metadataDependencyRegistry.registerDependency(
PhysicalTypeIdentifier.getMetadataIdentiferType(),
getProvidesType());
}
protected void deactivate(final ComponentContext context) {
metadataDependencyRegistry.deregisterDependency(
PhysicalTypeIdentifier.getMetadataIdentiferType(),
getProvidesType());
}
public MetadataItem get(final String metadataIdentificationString) {
final ClassOrInterfaceTypeDetails proxy = getGovernor(metadataIdentificationString);
if (proxy == null) {
return null;
}
final AnnotationMetadata proxyAnnotation = GwtUtils.getFirstAnnotation(
proxy, GwtUtils.PROXY_ANNOTATIONS);
if (proxyAnnotation == null) {
return null;
}
final String locatorType = GwtUtils.getStringValue(proxyAnnotation
.getAttribute("locator"));
if (StringUtils.isBlank(locatorType)) {
return null;
}
final ClassOrInterfaceTypeDetails entityType = gwtTypeService
.lookupEntityFromProxy(proxy);
if (entityType == null || Modifier.isAbstract(entityType.getModifier())) {
return null;
}
boolean useXmlConfiguration = false;
final ClassOrInterfaceTypeDetails existingLocator = gwtTypeService
.lookupLocatorFromEntity(entityType);
if (existingLocator != null) {
AnnotationMetadata annotation = existingLocator
.getAnnotation(RooJavaType.ROO_GWT_LOCATOR);
AnnotationAttributeValue<Boolean> attribute = annotation
.getAttribute("useXmlConfiguration");
if (attribute != null)
useXmlConfiguration = attribute.getValue();
}
final JavaType entity = entityType.getName();
final MethodMetadata identifierAccessor = persistenceMemberLocator
.getIdentifierAccessor(entity);
final MethodMetadata versionAccessor = persistenceMemberLocator
.getVersionAccessor(entity);
if (identifierAccessor == null || versionAccessor == null) {
return null;
}
final JavaType identifierType = GwtUtils.convertPrimitiveType(
identifierAccessor.getReturnType(), true);
final String locatorPhysicalTypeId = PhysicalTypeIdentifier
.createIdentifier(new JavaType(locatorType),
PhysicalTypeIdentifier.getPath(proxy
.getDeclaredByMetadataId()));
final ClassOrInterfaceTypeDetailsBuilder cidBuilder = new ClassOrInterfaceTypeDetailsBuilder(
locatorPhysicalTypeId);
final AnnotationMetadataBuilder annotationMetadataBuilder = new AnnotationMetadataBuilder(
RooJavaType.ROO_GWT_LOCATOR);
annotationMetadataBuilder.addStringAttribute("value",
entity.getFullyQualifiedTypeName());
if (useXmlConfiguration) {
annotationMetadataBuilder.addBooleanAttribute(
"useXmlConfiguration", true);
}
cidBuilder.addAnnotation(annotationMetadataBuilder);
if (!useXmlConfiguration) {
cidBuilder.addAnnotation(new AnnotationMetadataBuilder(
SpringJavaType.COMPONENT));
}
cidBuilder.setName(new JavaType(locatorType));
cidBuilder.setModifier(Modifier.PUBLIC);
cidBuilder.setPhysicalTypeCategory(PhysicalTypeCategory.CLASS);
cidBuilder.addExtendsTypes(new JavaType(LOCATOR
.getFullyQualifiedTypeName(), 0, DataType.TYPE, null, Arrays
.asList(entity, identifierType)));
cidBuilder.addMethod(getCreateMethod(locatorPhysicalTypeId, entity));
final MemberTypeAdditions findMethodAdditions = layerService
.getMemberTypeAdditions(locatorPhysicalTypeId,
CustomDataKeys.FIND_METHOD.name(), entity,
identifierType, LAYER_POSITION, !useXmlConfiguration,
new MethodParameter(identifierType, "id"));
JavaType potentialService = null;
FieldMetadata fieldMetadata = findMethodAdditions.getInvokedField();
if (fieldMetadata != null) {
potentialService = fieldMetadata.getFieldType();
}
Validate.notNull(findMethodAdditions,
"Find method not available for entity '%s'",
entity.getFullyQualifiedTypeName());
cidBuilder.addMethod(getFindMethod(findMethodAdditions, cidBuilder,
locatorPhysicalTypeId, entity, identifierType));
cidBuilder
.addMethod(getDomainTypeMethod(locatorPhysicalTypeId, entity));
cidBuilder.addMethod(getIdMethod(locatorPhysicalTypeId, entity,
identifierAccessor));
cidBuilder.addMethod(getIdTypeMethod(locatorPhysicalTypeId, entity,
identifierType));
cidBuilder.addMethod(getVersionMethod(locatorPhysicalTypeId, entity,
versionAccessor));
ClassOrInterfaceTypeDetails locator = cidBuilder.build();
// Adds or removes locator from XML configuration
if (useXmlConfiguration) {
gwtTemplateService.addLocatorToXmlConfiguration(locator,
potentialService);
}
else {
gwtTemplateService.removeLocatorFromXmlConfiguration(locator);
}
typeManagementService.createOrUpdateTypeOnDisk(cidBuilder.build());
return null;
}
private MethodMetadataBuilder getCreateMethod(final String declaredById,
final JavaType targetType) {
final InvocableMemberBodyBuilder invocableMemberBodyBuilder = InvocableMemberBodyBuilder
.getInstance();
invocableMemberBodyBuilder.append("return new "
+ targetType.getSimpleTypeName() + "();");
final MethodMetadataBuilder createMethodBuilder = new MethodMetadataBuilder(
declaredById, Modifier.PUBLIC, new JavaSymbolName("create"),
targetType, invocableMemberBodyBuilder);
final JavaType wildEntityType = new JavaType(
targetType.getFullyQualifiedTypeName(), 0, DataType.VARIABLE,
JavaType.WILDCARD_EXTENDS, null);
final JavaType classParameterType = new JavaType(
JavaType.CLASS.getFullyQualifiedTypeName(), 0, DataType.TYPE,
null, Arrays.asList(wildEntityType));
createMethodBuilder.addParameter("clazz", classParameterType);
return createMethodBuilder;
}
private MethodMetadataBuilder getDomainTypeMethod(
final String declaredById, final JavaType targetType) {
final InvocableMemberBodyBuilder invocableMemberBodyBuilder = InvocableMemberBodyBuilder
.getInstance();
invocableMemberBodyBuilder.append("return "
+ targetType.getSimpleTypeName() + ".class;");
final JavaType returnType = new JavaType(
CLASS.getFullyQualifiedTypeName(), 0, DataType.TYPE, null,
Arrays.asList(targetType));
return new MethodMetadataBuilder(declaredById, Modifier.PUBLIC,
new JavaSymbolName("getDomainType"), returnType,
invocableMemberBodyBuilder);
}
private MethodMetadataBuilder getFindMethod(
final MemberTypeAdditions findMethodAdditions,
final ClassOrInterfaceTypeDetailsBuilder locatorBuilder,
final String declaredById, final JavaType targetType,
final JavaType idType) {
final InvocableMemberBodyBuilder invocableMemberBodyBuilder = InvocableMemberBodyBuilder
.getInstance();
invocableMemberBodyBuilder.append("return ")
.append(findMethodAdditions.getMethodCall()).append(";");
findMethodAdditions.copyAdditionsTo(locatorBuilder,
locatorBuilder.build());
final MethodMetadataBuilder findMethodBuilder = new MethodMetadataBuilder(
declaredById, Modifier.PUBLIC, new JavaSymbolName("find"),
targetType, invocableMemberBodyBuilder);
final JavaType wildEntityType = new JavaType(
targetType.getFullyQualifiedTypeName(), 0, DataType.VARIABLE,
JavaType.WILDCARD_EXTENDS, null);
final JavaType classParameterType = new JavaType(
JavaType.CLASS.getFullyQualifiedTypeName(), 0, DataType.TYPE,
null, Arrays.asList(wildEntityType));
findMethodBuilder.addParameter("clazz", classParameterType);
findMethodBuilder.addParameter("id", idType);
return findMethodBuilder;
}
private ClassOrInterfaceTypeDetails getGovernor(
final String metadataIdentificationString) {
final JavaType governorTypeName = GwtLocatorMetadata
.getJavaType(metadataIdentificationString);
final LogicalPath governorTypePath = GwtLocatorMetadata
.getPath(metadataIdentificationString);
final String physicalTypeId = PhysicalTypeIdentifier.createIdentifier(
governorTypeName, governorTypePath);
return typeLocationService.getTypeDetails(physicalTypeId);
}
private MethodMetadataBuilder getIdMethod(final String declaredById,
final JavaType targetType, final MethodMetadata idAccessor) {
final InvocableMemberBodyBuilder invocableMemberBodyBuilder = InvocableMemberBodyBuilder
.getInstance();
invocableMemberBodyBuilder.append("return "
+ StringUtils.uncapitalize(targetType.getSimpleTypeName())
+ "." + idAccessor.getMethodName() + "();");
final MethodMetadataBuilder getIdMethod = new MethodMetadataBuilder(
declaredById,
Modifier.PUBLIC,
new JavaSymbolName("getId"),
GwtUtils.convertPrimitiveType(idAccessor.getReturnType(), true),
invocableMemberBodyBuilder);
getIdMethod.addParameter(
StringUtils.uncapitalize(targetType.getSimpleTypeName()),
targetType);
return getIdMethod;
}
private MethodMetadataBuilder getIdTypeMethod(final String declaredById,
final JavaType targetType, final JavaType idType) {
final InvocableMemberBodyBuilder invocableMemberBodyBuilder = InvocableMemberBodyBuilder
.getInstance();
invocableMemberBodyBuilder.append("return "
+ idType.getSimpleTypeName() + ".class;");
final JavaType returnType = new JavaType(
JavaType.CLASS.getFullyQualifiedTypeName(), 0, DataType.TYPE,
null, Arrays.asList(idType));
return new MethodMetadataBuilder(declaredById, Modifier.PUBLIC,
new JavaSymbolName("getIdType"), returnType,
invocableMemberBodyBuilder);
}
public String getProvidesType() {
return GwtLocatorMetadata.getMetadataIdentifierType();
}
private MethodMetadataBuilder getVersionMethod(final String declaredById,
final JavaType targetType, final MethodMetadata versionAccessor) {
final InvocableMemberBodyBuilder invocableMemberBodyBuilder = InvocableMemberBodyBuilder
.getInstance();
invocableMemberBodyBuilder.append("return "
+ StringUtils.uncapitalize(targetType.getSimpleTypeName())
+ "." + versionAccessor.getMethodName() + "();");
final MethodMetadataBuilder getIdMethodBuilder = new MethodMetadataBuilder(
declaredById, Modifier.PUBLIC,
new JavaSymbolName("getVersion"), JavaType.OBJECT,
invocableMemberBodyBuilder);
getIdMethodBuilder.addParameter(
StringUtils.uncapitalize(targetType.getSimpleTypeName()),
targetType);
return getIdMethodBuilder;
}
public void notify(final String upstreamDependency,
String downstreamDependency) {
if (MetadataIdentificationUtils
.isIdentifyingClass(downstreamDependency)) {
Validate.isTrue(
MetadataIdentificationUtils.getMetadataClass(
upstreamDependency).equals(
MetadataIdentificationUtils
.getMetadataClass(PhysicalTypeIdentifier
.getMetadataIdentiferType())),
"Expected class-level notifications only for PhysicalTypeIdentifier (not '%s')",
upstreamDependency);
final ClassOrInterfaceTypeDetails cid = typeLocationService
.getTypeDetails(upstreamDependency);
if (cid == null) {
return;
}
boolean processed = false;
if (cid.getAnnotation(RooJavaType.ROO_GWT_REQUEST) != null) {
final ClassOrInterfaceTypeDetails proxy = gwtTypeService
.lookupProxyFromRequest(cid);
if (proxy != null) {
final JavaType typeName = PhysicalTypeIdentifier
.getJavaType(proxy.getDeclaredByMetadataId());
final LogicalPath typePath = PhysicalTypeIdentifier
.getPath(proxy.getDeclaredByMetadataId());
downstreamDependency = GwtLocatorMetadata.createIdentifier(
typeName, typePath);
processed = true;
}
}
if (!processed
&& cid.getAnnotation(RooJavaType.ROO_GWT_PROXY) == null) {
boolean found = false;
for (final ClassOrInterfaceTypeDetails proxyCid : typeLocationService
.findClassesOrInterfaceDetailsWithAnnotation(RooJavaType.ROO_GWT_PROXY)) {
final AnnotationMetadata annotationMetadata = GwtUtils
.getFirstAnnotation(proxyCid,
GwtUtils.ROO_PROXY_REQUEST_ANNOTATIONS);
if (annotationMetadata != null) {
final AnnotationAttributeValue<?> attributeValue = annotationMetadata
.getAttribute("value");
if (attributeValue != null) {
final String mirrorName = GwtUtils
.getStringValue(attributeValue);
if (mirrorName != null
&& cid.getName()
.getFullyQualifiedTypeName()
.equals(attributeValue.getValue())) {
found = true;
final JavaType typeName = PhysicalTypeIdentifier
.getJavaType(proxyCid
.getDeclaredByMetadataId());
final LogicalPath typePath = PhysicalTypeIdentifier
.getPath(proxyCid
.getDeclaredByMetadataId());
downstreamDependency = GwtLocatorMetadata
.createIdentifier(typeName, typePath);
break;
}
}
}
}
if (!found) {
return;
}
}
else if (!processed) {
// A physical Java type has changed, and determine what the
// corresponding local metadata identification string would have
// been
final JavaType typeName = PhysicalTypeIdentifier
.getJavaType(upstreamDependency);
final LogicalPath typePath = PhysicalTypeIdentifier
.getPath(upstreamDependency);
downstreamDependency = GwtLocatorMetadata.createIdentifier(
typeName, typePath);
}
// We only need to proceed if the downstream dependency relationship
// is not already registered
// (if it's already registered, the event will be delivered directly
// later on)
if (metadataDependencyRegistry.getDownstream(upstreamDependency)
.contains(downstreamDependency)) {
return;
}
}
// We should now have an instance-specific "downstream dependency" that
// can be processed by this class
Validate.isTrue(
MetadataIdentificationUtils.getMetadataClass(
downstreamDependency).equals(
MetadataIdentificationUtils
.getMetadataClass(getProvidesType())),
"Unexpected downstream notification for '%s' to this provider (which uses '%s')",
downstreamDependency, getProvidesType());
metadataService.evictAndGet(downstreamDependency);
}
}