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); } }