package org.springframework.roo.addon.layers.service.addon;
import static org.springframework.roo.shell.OptionContexts.PROJECT;
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.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentContext;
import org.springframework.roo.addon.layers.repository.jpa.addon.RepositoryJpaLocator;
import org.springframework.roo.classpath.TypeLocationService;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.converters.JavaPackageConverter;
import org.springframework.roo.model.JavaPackage;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.model.RooJavaType;
import org.springframework.roo.project.LogicalPath;
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.Converter;
import org.springframework.roo.shell.ShellContext;
import org.springframework.roo.support.logging.HandlerUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class defines available commands to manage service layer. Allows to
* create new service related with some specific entity or generate services for
* every entity defined on generated project. Always generates interface and its
* implementation
*
* @author Stefan Schmidt
* @author Juan Carlos GarcĂa
* @since 1.2.0
*/
@Component
@Service
public class ServiceCommands implements CommandMarker {
private static final Logger LOGGER = HandlerUtils.getLogger(ServiceCommands.class);
//------------ OSGi component attributes ----------------
private BundleContext context;
@Reference
private ServiceOperations serviceOperations;
@Reference
private ProjectOperations projectOperations;
@Reference
private TypeLocationService typeLocationService;
@Reference
private RepositoryJpaLocator repositoryJpaLocator;
private Converter<JavaType> javaTypeConverter;
protected void activate(final ComponentContext context) {
this.context = context.getBundleContext();
}
@CliAvailabilityIndicator({"service"})
public boolean isServiceCommandAvailable() {
return serviceOperations.areServiceCommandsAvailable();
}
// ROO-3717: Service secure methods will be updated on future commits
/*@CliAvailabilityIndicator({ "service secure type", "service secure all" })
public boolean isSecureServiceCommandAvailable() {
return serviceOperations.isSecureServiceInstallationPossible();
}*/
@CliOptionVisibilityIndicator(
command = "service",
params = {"interface", "class", "repository"},
help = "--repository, --interface and --class parameters are not available if you don't specify --entity parameter")
public boolean areIterfaceAndClassVisible(ShellContext shellContext) {
// Get all defined parameters
Map<String, String> parameters = shellContext.getParameters();
// If --entity and --repository have been defined, show --class and --interface parameters
if (parameters.containsKey("entity") && StringUtils.isNotBlank(parameters.get("entity"))) {
return true;
}
return false;
}
@CliOptionMandatoryIndicator(command = "service", params = "repository")
public boolean isRepositoryParameterMandatory(ShellContext shellContext) {
if (shellContext.getParameters().containsKey("entity")
&& projectOperations.isMultimoduleProject()) {
return true;
}
return false;
}
@CliOptionMandatoryIndicator(command = "service", params = "interface")
public boolean isInterfaceParameterMandatory(ShellContext shellContext) {
if (shellContext.getParameters().containsKey("repository")
&& projectOperations.isMultimoduleProject()) {
return true;
}
return false;
}
@CliOptionAutocompleteIndicator(
command = "service",
param = "repository",
help = "--repository parameter must be the repository associated to the entity specified in --entity parameter. Please, write a valid value using autocomplete feature (TAB or CTRL + Space)")
public List<String> returnRepositories(ShellContext shellContext) {
// Get current value of class
String currentText = shellContext.getParameters().get("repository");
List<String> allPossibleValues = new ArrayList<String>();
// Get all defined parameters
Map<String, String> contextParameters = shellContext.getParameters();
// Getting entity
String entity = contextParameters.get("entity");
if (StringUtils.isNotBlank(entity)) {
JavaType domainEntity =
getJavaTypeConverter().convertFromText(entity, JavaType.class, PROJECT);
// Check if current entity has valid repository
Collection<ClassOrInterfaceTypeDetails> repositories =
repositoryJpaLocator.getRepositories(domainEntity);
for (ClassOrInterfaceTypeDetails repository : repositories) {
String replacedValue = replaceTopLevelPackageString(repository, currentText);
allPossibleValues.add(replacedValue);
}
if (allPossibleValues.isEmpty()) {
LOGGER
.log(
Level.INFO,
String
.format(
"ERROR: Entity '%s' does not have any repository generated. Use 'repository' commands to generate a valid repository and then try again.",
entity));
allPossibleValues.add("");
}
}
return allPossibleValues;
}
@CliOptionAutocompleteIndicator(command = "service", param = "entity",
help = "--entity option should be an entity.")
public List<String> getEntityPossibleResults(ShellContext shellContext) {
// Get current value of class
String currentText = shellContext.getParameters().get("entity");
List<String> allPossibleValues = new ArrayList<String>();
// Getting all existing entities
Set<ClassOrInterfaceTypeDetails> entitiesInProject =
typeLocationService.findClassesOrInterfaceDetailsWithAnnotation(RooJavaType.ROO_JPA_ENTITY);
for (ClassOrInterfaceTypeDetails entity : entitiesInProject) {
String name = replaceTopLevelPackageString(entity, currentText);
if (!allPossibleValues.contains(name)) {
allPossibleValues.add(name);
}
}
return allPossibleValues;
}
@CliOptionAutocompleteIndicator(command = "service", param = "interface",
help = "--interface option should be a new interface.", validate = false,
includeSpaceOnFinish = false)
public List<String> getInterfacePossibleResults(ShellContext shellContext) {
List<String> allPossibleValues = new ArrayList<String>();
// Add all modules to completions list
Collection<String> modules = projectOperations.getModuleNames();
for (String module : modules) {
if (StringUtils.isNotBlank(module)
&& !module.equals(projectOperations.getFocusedModule().getModuleName())) {
allPossibleValues.add(module.concat(LogicalPath.MODULE_PATH_SEPARATOR)
.concat(JavaPackageConverter.TOP_LEVEL_PACKAGE_SYMBOL).concat("."));
} else if (!projectOperations.isMultimoduleProject()) {
// Add only JavaPackage and JavaType completion
allPossibleValues.add(String.format("%s.service.api.",
JavaPackageConverter.TOP_LEVEL_PACKAGE_SYMBOL));
}
}
// Always add base package
allPossibleValues.add("~.");
return allPossibleValues;
}
@CliOptionAutocompleteIndicator(command = "service", param = "class",
help = "--class option should be a new class.", validate = false,
includeSpaceOnFinish = false)
public List<String> getClassPossibleResults(ShellContext shellContext) {
List<String> allPossibleValues = new ArrayList<String>();
// Add all modules to completions list
Collection<String> modules = projectOperations.getModuleNames();
for (String module : modules) {
if (StringUtils.isNotBlank(module)
&& !module.equals(projectOperations.getFocusedModule().getModuleName())) {
allPossibleValues.add(module.concat(LogicalPath.MODULE_PATH_SEPARATOR)
.concat(JavaPackageConverter.TOP_LEVEL_PACKAGE_SYMBOL).concat("."));
} else if (!projectOperations.isMultimoduleProject()) {
// Add only JavaPackage and JavaType completion
allPossibleValues.add(String.format("%s.service.impl.",
JavaPackageConverter.TOP_LEVEL_PACKAGE_SYMBOL));
}
}
// Always add base package
allPossibleValues.add("~.");
return allPossibleValues;
}
/**
* This indicator says if --apiPackage and --implPackage parameters are visible.
*
* If --all is specified, --apiPackage and --implPackage will be visible
*
* @param context ShellContext
* @return
*/
@CliOptionVisibilityIndicator(
params = {"apiPackage", "implPackage"},
command = "service",
help = "--apiPackage and --implPackage parameters are not visible if --all parameter hasn't been specified before.")
public boolean arePackageParametersVisible(ShellContext context) {
if (context.getParameters().containsKey("all")) {
return true;
}
return false;
}
/**
* This indicator says if --all parameter is visible.
*
* If --entity is specified, --all won't be visible
*
* @param context ShellContext
* @return
*/
@CliOptionVisibilityIndicator(params = {"all"}, command = "service",
help = "--all parameter is not visible if --entity parameter has been specified before.")
public boolean isAllParameterVisible(ShellContext context) {
if (context.getParameters().containsKey("entity")) {
return false;
}
return true;
}
/**
* This indicator says if --entity parameter is visible.
*
* If --all is specified, --entity won't be visible
*
* @param context ShellContext
* @return
*/
@CliOptionVisibilityIndicator(command = "service", params = {"entity"},
help = "--all parameter is not visible if --entity parameter has been specified before.")
public boolean isEntityParameterVisible(ShellContext context) {
if (context.getParameters().containsKey("all")) {
return false;
}
return true;
}
@CliCommand(
value = "service",
help = "Creates new service interface and its implementation related to an entity, or for all the "
+ "entities in generated project, with some basic management methods by using Spring Data repository methods.")
public void service(
@CliOption(
key = "all",
mandatory = false,
specifiedDefaultValue = "true",
unspecifiedDefaultValue = "false",
help = "Indicates if developer wants to generate service interfaces and their implementations "
+ "for every entity of current project. "
+ "This option is mandatory if `--entity` is not specified. Otherwise, using `--entity` "
+ "will cause the parameter `--all` won't be available. "
+ "Default if option present: `true`; default if option not present: `false`.") boolean all,
@CliOption(
key = "entity",
optionContext = PROJECT,
mandatory = false,
help = "The domain entity this service should manage. When working on a single module "
+ "project, simply specify the name of the entity. If you consider it necessary, you can "
+ "also specify the package. Ex.: `--class ~.domain.MyEntity` (where `~` is the base package). "
+ "When working with multiple modules, you should specify the name of the entity and the "
+ "module where it is. Ex.: `--class model:~.domain.MyEntity`. If the module is not specified, "
+ "it is assumed that the entity is in the module which has the focus. "
+ "Possible values are: any of the entities in the project. "
+ "This option is mandatory if `--all` is not specified. Otherwise, using `--all` "
+ "will cause the parameter `--entity` won't be available.") final JavaType domainType,
@CliOption(
key = "repository",
optionContext = PROJECT,
mandatory = true,
help = "The repository this service should expose. 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.: `--class ~.repository.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.: `--class repository:~.MyClass`. If the module is not "
+ "specified, it is assumed that the class is in the module which has the focus. "
+ "Possible values are: any of the repositories annotated with `@RooJpaRepository` and "
+ "associated to the entity specified in `--entity`. "
+ "This option is mandatory if `--entity` has been already specified and the project is "
+ "multi-module. "
+ "This option is available only when `--entity` has been specified. "
+ "Default if option not present: first repository annotated with `@RooJpaRepository` and "
+ "associated to the entity specified in `--entity`.") final JavaType repositoryType,
@CliOption(
key = "interface",
mandatory = true,
help = "The service interface to be generated. 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.: `--class ~.service.api.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.: `--class service-api:~.MyClass`. If the module is not specified, it "
+ "is assumed that the class is in the module which has the focus. "
+ "This option is mandatory if `--entity` has been already specified and the project is "
+ "multi-module. "
+ "This option is available only when `--entity` has been specified. "
+ "Default if option not present: concatenation of entity simple name with 'Service' in "
+ "`~.service.api` package, or 'service-api:~.' if multi-module project.") final JavaType interfaceType,
@CliOption(
key = "class",
mandatory = false,
help = "The service implementation to be generated. 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.: `--class ~.service.impl.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.: `--class service-impl:~.MyClass`. If the module is not "
+ "specified, it is assumed that the class is in the module which has the focus. "
+ "This option is available only when `--entity` has been specified. "
+ "Default if option not present: concatenation of entity simple name with 'ServiceImpl' "
+ "in `~.service.impl` package, or 'service-impl:~.' if multi-module project.") final JavaType implType,
@CliOption(
key = "apiPackage",
mandatory = false,
help = "The java interface package. In multi-module project you should specify the module name"
+ " before the package name. Ex.: `--apiPackage service-api:org.springframework.roo` but, "
+ "if module name is not present, the Roo Shell focused module will be used. "
+ "This option is available only when `--all` parameter has been specified. "
+ "Default value if not present: `~.service.api` package, or 'service-api:~.' if "
+ "multi-module project.") JavaPackage apiPackage,
@CliOption(
key = "implPackage",
mandatory = false,
help = "The java package of the implementation classes for the interfaces. In multi-module "
+ "project you should specify the module name before the package name. Ex.: `--implPackage "
+ "service-impl:org.springframework.roo` but, if module name is not present, the Roo Shell "
+ "focused module will be used. "
+ "This option is available only when `--all` parameter has been specified. "
+ "Default value if not present: `~.service.impl` package, or 'service-impl:~.' if "
+ "multi-module project.") JavaPackage implPackage) {
if (all) {
// If user didn't specified some API package, use default API package
if (apiPackage == null) {
if (projectOperations.isMultimoduleProject()) {
// Build default JavaPackage with module
for (String moduleName : projectOperations.getModuleNames()) {
if (moduleName.equals("service-api")) {
Pom module = projectOperations.getPomFromModuleName(moduleName);
apiPackage =
new JavaPackage(typeLocationService.getTopLevelPackageForModule(module),
moduleName);
break;
}
}
// Check if repository found
Validate.notNull(apiPackage, "Couldn't find in project a default service.api "
+ "package. Please, use 'apiPackage' option to specify it.");
} else {
// Build default JavaPackage for single module
apiPackage =
new JavaPackage(projectOperations.getFocusedTopLevelPackage()
.getFullyQualifiedPackageName().concat(".service.api"),
projectOperations.getFocusedModuleName());
}
}
// If user didn't specified some impl package, use default impl package
if (implPackage == null) {
if (projectOperations.isMultimoduleProject()) {
// Build default JavaPackage with module
for (String moduleName : projectOperations.getModuleNames()) {
if (moduleName.equals("service-impl")) {
Pom module = projectOperations.getPomFromModuleName(moduleName);
implPackage =
new JavaPackage(typeLocationService.getTopLevelPackageForModule(module),
moduleName);
break;
}
}
// Check if repository found
Validate.notNull(implPackage, "Couldn't find in project a default service.impl "
+ "package. Please, use 'implPackage' option to specify it.");
} else {
// Build default JavaPackage for single module
implPackage =
new JavaPackage(projectOperations.getFocusedTopLevelPackage()
.getFullyQualifiedPackageName().concat(".service.impl"),
projectOperations.getFocusedModuleName());
}
}
serviceOperations.addAllServices(apiPackage, implPackage);
} else {
serviceOperations.addService(domainType, repositoryType, interfaceType, implType);
}
}
/**
* 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;
}
@SuppressWarnings("unchecked")
public Converter<JavaType> getJavaTypeConverter() {
if (javaTypeConverter == null) {
// Get all Services implement JavaTypeConverter interface
try {
ServiceReference<?>[] references =
this.context.getAllServiceReferences(Converter.class.getName(), null);
for (ServiceReference<?> ref : references) {
Converter<?> converter = (Converter<?>) this.context.getService(ref);
if (converter.supports(JavaType.class, PROJECT)) {
javaTypeConverter = (Converter<JavaType>) converter;
return javaTypeConverter;
}
}
return null;
} catch (InvalidSyntaxException e) {
LOGGER.warning("ERROR: Cannot load JavaTypeConverter on FinderCommands.");
return null;
}
} else {
return javaTypeConverter;
}
}
}