package org.springframework.roo.addon.webflow;
import static org.springframework.roo.shell.OptionContexts.APPLICATION_FEATURE_INCLUDE_CURRENT_MODULE;
import org.apache.commons.lang3.StringUtils;
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.ModuleFeatureName;
import org.springframework.roo.classpath.TypeLocationService;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.model.JdkJavaType;
import org.springframework.roo.model.RooJavaType;
import org.springframework.roo.process.manager.FileManager;
import org.springframework.roo.project.LogicalPath;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.PathResolver;
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.CliOptionAutocompleteIndicator;
import org.springframework.roo.shell.CliOptionMandatoryIndicator;
import org.springframework.roo.shell.CliOptionVisibilityIndicator;
import org.springframework.roo.shell.CommandMarker;
import org.springframework.roo.shell.ShellContext;
import org.springframework.roo.shell.SimpleParser;
import org.springframework.roo.support.logging.HandlerUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
/**
* Commands for the 'web flow' add-on to be used by the Roo shell.
*
* @author Stefan Schmidt
* @author Sergio Clares
*
* @since 1.0
*/
@Component
@Service
public class WebFlowCommands implements CommandMarker {
private static final Logger LOGGER = HandlerUtils.getLogger(SimpleParser.class);
@Reference
private WebFlowOperations webFlowOperations;
@Reference
private ProjectOperations projectOperations;
@Reference
private TypeLocationService typeLocationService;
@Reference
private PathResolver pathResolver;
@Reference
private FileManager fileManager;
@CliAvailabilityIndicator("web flow")
public boolean isInstallWebFlowAvailable() {
return webFlowOperations.isWebFlowInstallationPossible();
}
@CliOptionMandatoryIndicator(command = "web flow", params = "module")
public boolean isModuleRequired(ShellContext shellContext) {
Pom module = projectOperations.getFocusedModule();
if (!isModuleVisible(shellContext)
|| typeLocationService.hasModuleFeature(module, ModuleFeatureName.APPLICATION)) {
return false;
}
return true;
}
@CliOptionAutocompleteIndicator(command = "web flow", param = "module",
help = "You should specify an existing module name for option --module.")
public List<String> getModulePossibleValues(ShellContext shellContext) {
List<String> possibleValues = new ArrayList<String>();
possibleValues.addAll(projectOperations.getModuleNames());
return possibleValues;
}
@CliOptionVisibilityIndicator(command = "web flow", params = {"module"},
help = "--module option is not visible when the project is a multimodule project.")
public boolean isModuleVisible(ShellContext shellContext) {
if (typeLocationService.getModuleNames(ModuleFeatureName.APPLICATION).size() > 1) {
return true;
}
return false;
}
@CliOptionAutocompleteIndicator(command = "web flow", param = "class",
help = "You should specify an existing and serializable class for option " + "'--class'.",
validate = false)
public List<String> getClassPossibleValues(ShellContext shellContext) {
// Get current value of class
String currentText = shellContext.getParameters().get("class");
List<String> allPossibleValues = new ArrayList<String>();
// Getting all existing entities
Set<ClassOrInterfaceTypeDetails> domainClassesInProject =
typeLocationService.findClassesOrInterfaceDetailsWithAnnotation(RooJavaType.ROO_JPA_ENTITY,
RooJavaType.ROO_DTO);
for (ClassOrInterfaceTypeDetails classDetails : domainClassesInProject) {
// Check if class implements serializable (needed for WebFlow)
boolean isSerializable = false;
// First, chech for @RooSerializable
if (classDetails.getAnnotation(RooJavaType.ROO_SERIALIZABLE) != null) {
isSerializable = true;
}
// Check for the explicit 'implements Serializable'
if (!isSerializable) {
List<JavaType> implementsTypes = classDetails.getImplementsTypes();
for (JavaType type : implementsTypes) {
if (type.equals(JdkJavaType.SERIALIZABLE)) {
isSerializable = true;
break;
}
}
}
if (isSerializable) {
// Add to possible values
String name = replaceTopLevelPackageString(classDetails, currentText);
if (!allPossibleValues.contains(name)) {
allPossibleValues.add(name);
}
}
}
if (allPossibleValues.isEmpty()) {
// Any entity or DTO in project is serializable
LOGGER.info("Any auto-complete value offered because the project hasn't any entity "
+ "or DTO which implement Serializable");
}
return allPossibleValues;
}
@CliCommand(value = "web flow", help = "Installs a Spring Web Flow into your project.")
public void addWebFlow(
@CliOption(key = {"flowName"}, mandatory = true, help = "The name for your web flow.") final String flowName,
@CliOption(
key = {"module"},
mandatory = true,
help = "The application module where create the web flow. "
+ "This option is mandatory if the focus is not set in an 'application' module and "
+ "there are more than one 'application' modules, that is, a module containing an "
+ "`@SpringBootApplication` class. "
+ "This option is available only if there are more than one application module and none of"
+ " them is focused. "
+ "Default if option not present: the unique 'application' module, or focused 'application'"
+ " module.", unspecifiedDefaultValue = ".",
optionContext = APPLICATION_FEATURE_INCLUDE_CURRENT_MODULE) Pom module,
@CliOption(key = {"class"}, mandatory = false,
help = "The class used to create the model object this flow is mainly "
+ "bound to. Can be an entity or a DTO and must be serializable.") final JavaType klass) {
if (module == null) {
// Get focused module
module = projectOperations.getFocusedModule();
}
// Check if exists other entity with the same name
if (klass != null) {
final String fiilePathIdentifier =
pathResolver.getCanonicalPath(klass.getModule(), Path.SRC_MAIN_JAVA, klass);
if (!fileManager.exists(fiilePathIdentifier)) {
throw new IllegalArgumentException(String.format(
"Class '%s' doesn't exist. Try to use a different class name on "
+ "--class parameter. You can use 'dto' or 'entity jpa' command to "
+ "create it. It needs to be serializable.", klass));
}
}
webFlowOperations.installWebFlow(flowName, module.getModuleName(), klass);
}
/**
* Replaces a JavaType fullyQualifiedName for a shorter name using '~' for TopLevelPackage
*
* @param cid ClassOrInterfaceTypeDetails of a JavaType
* @param currentText String current text for option value
* @return the String representing a JavaType with its name shortened
*/
private String replaceTopLevelPackageString(ClassOrInterfaceTypeDetails cid, String currentText) {
String javaTypeFullyQualilfiedName = cid.getType().getFullyQualifiedTypeName();
String javaTypeString = "";
String topLevelPackageString = "";
// Add module value to topLevelPackage when necessary
if (StringUtils.isNotBlank(cid.getType().getModule())
&& !cid.getType().getModule().equals(projectOperations.getFocusedModuleName())) {
// Target module is not focused
javaTypeString = cid.getType().getModule().concat(LogicalPath.MODULE_PATH_SEPARATOR);
topLevelPackageString =
projectOperations.getTopLevelPackage(cid.getType().getModule())
.getFullyQualifiedPackageName();
} else if (StringUtils.isNotBlank(cid.getType().getModule())
&& cid.getType().getModule().equals(projectOperations.getFocusedModuleName())
&& (currentText.startsWith(cid.getType().getModule()) || cid.getType().getModule()
.startsWith(currentText)) && StringUtils.isNotBlank(currentText)) {
// Target module is focused but user wrote it
javaTypeString = cid.getType().getModule().concat(LogicalPath.MODULE_PATH_SEPARATOR);
topLevelPackageString =
projectOperations.getTopLevelPackage(cid.getType().getModule())
.getFullyQualifiedPackageName();
} else {
// Not multimodule project
topLevelPackageString =
projectOperations.getFocusedTopLevelPackage().getFullyQualifiedPackageName();
}
// Autocomplete with abbreviate or full qualified mode
String auxString =
javaTypeString.concat(StringUtils.replace(javaTypeFullyQualilfiedName,
topLevelPackageString, "~"));
if ((StringUtils.isBlank(currentText) || auxString.startsWith(currentText))
&& StringUtils.contains(javaTypeFullyQualilfiedName, topLevelPackageString)) {
// Value is for autocomplete only or user wrote abbreviate value
javaTypeString = auxString;
} else {
// Value could be for autocomplete or for validation
javaTypeString = String.format("%s%s", javaTypeString, javaTypeFullyQualilfiedName);
}
return javaTypeString;
}
}