package org.springframework.roo.classpath;
import static org.apache.commons.io.IOUtils.LINE_SEPARATOR;
import static org.springframework.roo.shell.OptionContexts.INCLUDE_CURRENT_MODULE;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ObjectUtils;
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.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.MemberHoldingTypeDetails;
import org.springframework.roo.classpath.scanner.MemberDetailsScanner;
import org.springframework.roo.metadata.MetadataDependencyRegistry;
import org.springframework.roo.metadata.MetadataIdentificationUtils;
import org.springframework.roo.metadata.MetadataLogger;
import org.springframework.roo.metadata.MetadataService;
import org.springframework.roo.metadata.MetadataTimingStatistic;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.project.ProjectMetadata;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.project.maven.Pom;
import org.springframework.roo.shell.CliAvailabilityIndicator;
import org.springframework.roo.shell.CliCommand;
import org.springframework.roo.shell.CliOption;
import org.springframework.roo.shell.CommandMarker;
@Component
@Service
public class MetadataCommands implements CommandMarker {
private static final String METADATA_FOR_MODULE_COMMAND = "metadata for module";
private static final String METADATA_CACHE_COMMAND = "metadata cache";
private static final String METADATA_FOR_ID_COMMAND = "metadata for id";
private static final String METADATA_FOR_TYPE_COMMAND = "metadata for type";
private static final String METADATA_STATUS_COMMAND = "metadata status";
private static final String METADATA_TRACE_COMMAND = "metadata trace";
@Reference
private MemberDetailsScanner memberDetailsScanner;
@Reference
private MetadataDependencyRegistry metadataDependencyRegistry;
@Reference
private MetadataLogger metadataLogger;
@Reference
private MetadataService metadataService;
@Reference
private ProjectOperations projectOperations;
@Reference
private TypeLocationService typeLocationService;
@CliAvailabilityIndicator({METADATA_FOR_MODULE_COMMAND, METADATA_CACHE_COMMAND,
METADATA_FOR_ID_COMMAND, METADATA_FOR_TYPE_COMMAND, METADATA_STATUS_COMMAND,
METADATA_TRACE_COMMAND})
public boolean isModuleMetadataAvailable() {
return projectOperations.getFocusedModule() != null;
}
@CliCommand(value = METADATA_CACHE_COMMAND,
help = "Shows detailed metadata for the indicated type.")
public String metadataCacheMaximum(@CliOption(key = {"maximumCapacity"}, mandatory = true,
help = "The maximum number of metadata items to cache.") final int maxCapacity) {
Validate.isTrue(maxCapacity >= 100, "Maximum capacity must be 100 or greater");
metadataService.setMaxCapacity(maxCapacity);
// Show them that the change has taken place
return metadataTimings();
}
@CliCommand(value = METADATA_FOR_ID_COMMAND,
help = "Shows detailed information about the metadata item.")
public String metadataForId(@CliOption(key = {"", "metadataId"}, mandatory = true,
help = "The metadata ID (should start with MID:).") final String metadataId) {
final StringBuilder sb = new StringBuilder();
sb.append("Identifier : ").append(metadataId).append(IOUtils.LINE_SEPARATOR);
for (final String upstreamId : metadataDependencyRegistry.getUpstream(metadataId)) {
sb.append("Upstream : ").append(upstreamId).append(LINE_SEPARATOR);
}
// Include any "class level" notifications that this instance would
// receive (useful for debugging)
// Only necessary if the ID doesn't already represent a class (as such
// dependencies would have been listed earlier)
if (!MetadataIdentificationUtils.isIdentifyingClass(metadataId)) {
final String mdClassId = MetadataIdentificationUtils.getMetadataClassId(metadataId);
for (final String upstreamId : metadataDependencyRegistry.getUpstream(mdClassId)) {
sb.append("Upstream : ").append(upstreamId).append(" (via MD class)")
.append(LINE_SEPARATOR);
}
}
for (final String downstreamId : metadataDependencyRegistry.getDownstream(metadataId)) {
sb.append("Downstream : ").append(downstreamId).append(LINE_SEPARATOR);
}
// Include any notifications that this class of metadata would trigger
// (useful for debugging)
// Only necessary if the ID doesn't already represent a class (as such
// dependencies would have been listed earlier)
if (!MetadataIdentificationUtils.isIdentifyingClass(metadataId)) {
final String mdClassId = MetadataIdentificationUtils.getMetadataClassId(metadataId);
for (final String downstreamId : metadataDependencyRegistry.getDownstream(mdClassId)) {
sb.append("Downstream : ").append(downstreamId).append(" (via MD class)")
.append(LINE_SEPARATOR);
}
}
if (MetadataIdentificationUtils.isIdentifyingInstance(metadataId)) {
sb.append("Metadata : ").append(metadataService.get(metadataId));
}
return sb.toString();
}
@CliCommand(value = METADATA_FOR_MODULE_COMMAND,
help = "Shows the ProjectMetadata for the indicated project module.")
public String metadataForModule(@CliOption(key = {"", "module"}, mandatory = false,
optionContext = INCLUDE_CURRENT_MODULE,
help = "The module for which to retrieve the metadata. "
+ "Default if option not present: the Roo Shell focused module.") final Pom pom) {
final Pom targetPom = ObjectUtils.defaultIfNull(pom, projectOperations.getFocusedModule());
if (targetPom == null) {
return "This project has no modules";
}
final String projectMID = ProjectMetadata.getProjectIdentifier(targetPom.getModuleName());
return metadataService.get(projectMID).toString();
}
@CliCommand(value = METADATA_FOR_TYPE_COMMAND,
help = "Shows detailed metadata for the indicated type.")
public String metadataForType(
@CliOption(
key = {"", "type"},
mandatory = true,
help = "The Java type for which to display metadata. When working on a single module "
+ "project, simply specify the name of the class. If you consider it necessary, you can "
+ "also specify the package. Ex.: `--type ~.domain.MyClass` (where `~` is the base package). "
+ "When working with multiple modules, you should specify the name of the class and the "
+ "module where it is. Ex.: `--type model:~.domain.MyClass`. If the module is not "
+ "specified, it is assumed that the class is in the module which has the focus.") final JavaType javaType) {
final String id = typeLocationService.getPhysicalTypeIdentifier(javaType);
if (id == null) {
return "Cannot locate source for " + javaType.getFullyQualifiedTypeName();
}
final StringBuilder sb = new StringBuilder();
sb.append("Java Type : ").append(javaType.getFullyQualifiedTypeName())
.append(System.getProperty("line.separator"));
final ClassOrInterfaceTypeDetails javaTypeDetails =
typeLocationService.getTypeDetails(javaType);
if (javaTypeDetails == null) {
sb.append("Java type details unavailable").append(System.getProperty("line.separator"));
} else {
for (final MemberHoldingTypeDetails holder : memberDetailsScanner.getMemberDetails(
getClass().getName(), javaTypeDetails).getDetails()) {
sb.append("Member scan: ").append(holder.getDeclaredByMetadataId())
.append(System.getProperty("line.separator"));
}
}
sb.append(metadataForId(id));
return sb.toString();
}
@CliCommand(value = METADATA_STATUS_COMMAND,
help = "Shows metadata statistics of the current project.")
public String metadataTimings() {
final StringBuilder sb = new StringBuilder();
for (final MetadataTimingStatistic stat : metadataLogger.getTimings()) {
sb.append(stat.toString()).append(LINE_SEPARATOR);
}
sb.append(metadataService.toString());
return sb.toString();
}
@CliCommand(value = METADATA_TRACE_COMMAND,
help = "Traces metadata event delivery notifications.")
public void metadataTrace(@CliOption(key = {"", "level"}, mandatory = true,
help = "The verbosity of notifications (0=none, 1=some, 2=all).") final int level) {
metadataLogger.setTraceLevel(level);
}
}