package org.springframework.roo.addon.gwt.scaffold;
import java.io.File;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
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.GwtFileManager;
import org.springframework.roo.addon.gwt.GwtPath;
import org.springframework.roo.addon.gwt.GwtProxyProperty;
import org.springframework.roo.addon.gwt.GwtTemplateDataHolder;
import org.springframework.roo.addon.gwt.GwtTemplateService;
import org.springframework.roo.addon.gwt.GwtType;
import org.springframework.roo.addon.gwt.GwtTypeService;
import org.springframework.roo.addon.gwt.GwtUtils;
import org.springframework.roo.classpath.PhysicalTypeIdentifier;
import org.springframework.roo.classpath.TypeLocationService;
import org.springframework.roo.classpath.details.BeanInfoUtils;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.FieldMetadata;
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.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.JavaPackage;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.model.RooJavaType;
import org.springframework.roo.project.LogicalPath;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.PathResolver;
import org.springframework.roo.project.ProjectOperations;
/**
* Monitors Java types and if necessary creates/updates/deletes the GWT files
* maintained for each mirror-compatible object. You can find a list of
* mirror-compatible objects in
* {@link org.springframework.roo.addon.gwt.GwtType}.
* <p/>
* <p/>
* For now only @RooJpaEntity instances will be mirror-compatible.
* <p/>
* <p/>
* Like all Roo add-ons, this provider aims to expose potentially-useful
* contents of the above files via {@link GwtScaffoldMetadata}. It also attempts
* to avoiding writing to disk unless actually necessary.
* <p/>
* <p/>
* A separate type monitors the creation/deletion of the aforementioned files to
* maintain "global indexes".
*
* @author Ben Alex
* @author Alan Stewart
* @author Ray Cromwell
* @author Amit Manjhi
* @since 1.1
*/
@Component(immediate = true)
@Service
public class GwtScaffoldMetadataProviderImpl implements
GwtScaffoldMetadataProvider {
@Reference protected GwtFileManager gwtFileManager;
@Reference protected GwtTemplateService gwtTemplateService;
@Reference protected GwtTypeService gwtTypeService;
@Reference protected MetadataDependencyRegistry metadataDependencyRegistry;
@Reference protected MetadataService metadataService;
@Reference protected ProjectOperations projectOperations;
@Reference protected TypeLocationService typeLocationService;
protected void activate(final ComponentContext context) {
metadataDependencyRegistry.registerDependency(
PhysicalTypeIdentifier.getMetadataIdentiferType(),
getProvidesType());
}
private void buildType(final GwtType type, final String moduleName) {
gwtTypeService.buildType(type, gwtTemplateService
.getStaticTemplateTypeDetails(type, moduleName), moduleName);
}
private String createLocalIdentifier(final JavaType javaType,
final LogicalPath path) {
return GwtScaffoldMetadata.createIdentifier(javaType, path);
}
protected void deactivate(final ComponentContext context) {
metadataDependencyRegistry.deregisterDependency(
PhysicalTypeIdentifier.getMetadataIdentiferType(),
getProvidesType());
}
public MetadataItem get(final String metadataIdentificationString) {
// Obtain the governor's information
final ClassOrInterfaceTypeDetails mirroredType = getGovernor(metadataIdentificationString);
if (mirroredType == null
|| Modifier.isAbstract(mirroredType.getModifier())) {
return null;
}
final ClassOrInterfaceTypeDetails proxy = gwtTypeService
.lookupProxyFromEntity(mirroredType);
if (proxy == null || proxy.getDeclaredMethods().isEmpty()) {
return null;
}
final ClassOrInterfaceTypeDetails request = gwtTypeService
.lookupRequestFromEntity(mirroredType);
if (request == null) {
return null;
}
if (!GwtUtils.getBooleanAnnotationValue(proxy,
RooJavaType.ROO_GWT_PROXY, "scaffold", false)) {
return null;
}
final String moduleName = PhysicalTypeIdentifier.getPath(
proxy.getDeclaredByMetadataId()).getModule();
buildType(GwtType.APP_ENTITY_TYPES_PROCESSOR, moduleName);
buildType(GwtType.APP_REQUEST_FACTORY, moduleName);
buildType(GwtType.LIST_PLACE_RENDERER, moduleName);
buildType(GwtType.MASTER_ACTIVITIES, moduleName);
buildType(GwtType.LIST_PLACE_RENDERER, moduleName);
buildType(GwtType.DETAILS_ACTIVITIES, moduleName);
buildType(GwtType.MOBILE_ACTIVITIES, moduleName);
final GwtScaffoldMetadata gwtScaffoldMetadata = new GwtScaffoldMetadata(
metadataIdentificationString);
final JavaPackage topLevelPackage = projectOperations
.getTopLevelPackage(moduleName);
final Map<JavaSymbolName, GwtProxyProperty> clientSideTypeMap = new LinkedHashMap<JavaSymbolName, GwtProxyProperty>();
for (final MethodMetadata proxyMethod : proxy.getDeclaredMethods()) {
if (!proxyMethod.getMethodName().getSymbolName().startsWith("get")) {
continue;
}
final JavaSymbolName propertyName = new JavaSymbolName(
StringUtils.uncapitalize(BeanInfoUtils
.getPropertyNameForJavaBeanMethod(proxyMethod)
.getSymbolName()));
final JavaType propertyType = proxyMethod.getReturnType();
ClassOrInterfaceTypeDetails ptmd = typeLocationService
.getTypeDetails(propertyType);
if (propertyType.isCommonCollectionType()
&& !propertyType.getParameters().isEmpty()) {
ptmd = typeLocationService.getTypeDetails(propertyType
.getParameters().get(0));
}
final FieldMetadata field = proxy.getDeclaredField(propertyName);
final List<AnnotationMetadata> annotations = field != null ? field
.getAnnotations() : Collections
.<AnnotationMetadata> emptyList();
final GwtProxyProperty gwtProxyProperty = new GwtProxyProperty(
topLevelPackage, ptmd, propertyType,
propertyName.getSymbolName(), annotations, proxyMethod
.getMethodName().getSymbolName());
clientSideTypeMap.put(propertyName, gwtProxyProperty);
}
final GwtTemplateDataHolder templateDataHolder = gwtTemplateService
.getMirrorTemplateTypeDetails(mirroredType, clientSideTypeMap,
moduleName);
final Map<GwtType, List<ClassOrInterfaceTypeDetails>> typesToBeWritten = new LinkedHashMap<GwtType, List<ClassOrInterfaceTypeDetails>>();
final Map<String, String> xmlToBeWritten = new LinkedHashMap<String, String>();
final Map<GwtType, JavaType> mirrorTypeMap = GwtUtils.getMirrorTypeMap(
mirroredType.getName(), topLevelPackage);
mirrorTypeMap.put(GwtType.PROXY, proxy.getName());
mirrorTypeMap.put(GwtType.REQUEST, request.getName());
for (final Map.Entry<GwtType, JavaType> entry : mirrorTypeMap
.entrySet()) {
final GwtType gwtType = entry.getKey();
final JavaType javaType = entry.getValue();
if (!gwtType.isMirrorType() || gwtType.equals(GwtType.PROXY)
|| gwtType.equals(GwtType.REQUEST)) {
continue;
}
gwtType.dynamicallyResolveFieldsToWatch(clientSideTypeMap);
gwtType.dynamicallyResolveMethodsToWatch(proxy.getName(),
clientSideTypeMap, topLevelPackage);
final List<MemberHoldingTypeDetails> extendsTypes = gwtTypeService
.getExtendsTypes(templateDataHolder
.getTemplateTypeDetailsMap().get(gwtType));
typesToBeWritten.put(gwtType, gwtTypeService
.buildType(gwtType, templateDataHolder
.getTemplateTypeDetailsMap().get(gwtType),
extendsTypes, moduleName));
if (gwtType.isCreateUiXml()) {
final GwtPath gwtPath = gwtType.getPath();
final PathResolver pathResolver = projectOperations
.getPathResolver();
final String webappPath = pathResolver.getIdentifier(
LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP,
moduleName), moduleName);
final String packagePath = pathResolver
.getIdentifier(LogicalPath.getInstance(
Path.SRC_MAIN_JAVA, moduleName), gwtPath
.getPackagePath(topLevelPackage));
final String targetDirectory = gwtPath == GwtPath.WEB ? webappPath
: packagePath;
final String destFile = targetDirectory + File.separatorChar
+ javaType.getSimpleTypeName() + ".ui.xml";
final String contents = gwtTemplateService.buildUiXml(
templateDataHolder.getXmlTemplates().get(gwtType),
destFile,
new ArrayList<MethodMetadata>(proxy
.getDeclaredMethods()));
xmlToBeWritten.put(destFile, contents);
}
}
// Our general strategy is to instantiate GwtScaffoldMetadata, which
// offers a conceptual representation of what should go into the 4
// key-specific types; after that we do comparisons and write to disk if
// needed
for (final Map.Entry<GwtType, List<ClassOrInterfaceTypeDetails>> entry : typesToBeWritten
.entrySet()) {
gwtFileManager.write(typesToBeWritten.get(entry.getKey()), entry
.getKey().isOverwriteConcrete());
}
for (final ClassOrInterfaceTypeDetails type : templateDataHolder
.getTypeList()) {
gwtFileManager.write(type, false);
}
for (final Map.Entry<String, String> entry : xmlToBeWritten.entrySet()) {
gwtFileManager.write(entry.getKey(), entry.getValue());
}
for (final Map.Entry<String, String> entry : templateDataHolder
.getXmlMap().entrySet()) {
gwtFileManager.write(entry.getKey(), entry.getValue());
}
return gwtScaffoldMetadata;
}
private ClassOrInterfaceTypeDetails getGovernor(
final String metadataIdentificationString) {
final JavaType governorTypeName = GwtScaffoldMetadata
.getJavaType(metadataIdentificationString);
final LogicalPath governorTypePath = GwtScaffoldMetadata
.getPath(metadataIdentificationString);
final String physicalTypeId = PhysicalTypeIdentifier.createIdentifier(
governorTypeName, governorTypePath);
return typeLocationService.getTypeDetails(physicalTypeId);
}
public String getProvidesType() {
return GwtScaffoldMetadata.getMetadataIdentifierType();
}
public void notify(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 '"
+ upstreamDependency + "')");
final ClassOrInterfaceTypeDetails cid = typeLocationService
.getTypeDetails(upstreamDependency);
if (cid == null) {
return;
}
if (cid.getAnnotation(RooJavaType.ROO_GWT_PROXY) != null) {
final ClassOrInterfaceTypeDetails entityType = gwtTypeService
.lookupEntityFromProxy(cid);
if (entityType != null) {
upstreamDependency = entityType.getDeclaredByMetadataId();
}
}
else if (cid.getAnnotation(RooJavaType.ROO_GWT_REQUEST) != null) {
final ClassOrInterfaceTypeDetails entityType = gwtTypeService
.lookupEntityFromRequest(cid);
if (entityType != null) {
upstreamDependency = entityType.getDeclaredByMetadataId();
}
}
else if (cid.getAnnotation(RooJavaType.ROO_GWT_LOCATOR) != null) {
final ClassOrInterfaceTypeDetails entityType = gwtTypeService
.lookupEntityFromLocator(cid);
if (entityType != null) {
upstreamDependency = entityType.getDeclaredByMetadataId();
}
}
// 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 = createLocalIdentifier(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 '"
+ downstreamDependency
+ "' to this provider (which uses '"
+ getProvidesType() + "'");
metadataService.evictAndGet(downstreamDependency);
}
}