package org.springframework.roo.addon.web.mvc.controller.addon;
import static org.springframework.roo.shell.OptionContexts.APPLICATION_FEATURE;
import static org.springframework.roo.shell.OptionContexts.PROJECT;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.Component;
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.web.mvc.controller.addon.finder.SearchAnnotationValues;
import org.springframework.roo.addon.web.mvc.controller.addon.responses.ControllerMVCResponseService;
import org.springframework.roo.addon.web.mvc.controller.annotations.ControllerType;
import org.springframework.roo.classpath.ModuleFeatureName;
import org.springframework.roo.classpath.TypeLocationService;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.FieldMetadata;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.operations.ClasspathOperations;
import org.springframework.roo.classpath.scanner.MemberDetails;
import org.springframework.roo.classpath.scanner.MemberDetailsScanner;
import org.springframework.roo.converters.JavaTypeConverter;
import org.springframework.roo.model.JavaPackage;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.model.JpaJavaType;
import org.springframework.roo.model.RooJavaType;
import org.springframework.roo.process.manager.FileManager;
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.Converter;
import org.springframework.roo.shell.ShellContext;
import org.springframework.roo.support.logging.HandlerUtils;
import org.springframework.roo.support.osgi.ServiceInstaceManager;
/**
* This class provides necessary commands to be able to include Spring MVC on
* generated project and generate new controllers.
*
* @author Stefan Schmidt
* @author Juan Carlos GarcĂa
* @author Paula Navarro
* @since 1.0
*/
@Component
@Service
public class ControllerCommands implements CommandMarker {
private static Logger LOGGER = HandlerUtils.getLogger(ControllerCommands.class);
// ------------ OSGi component attributes ----------------
private BundleContext context;
private Map<String, ControllerMVCResponseService> responseTypes =
new HashMap<String, ControllerMVCResponseService>();
private ServiceInstaceManager serviceInstaceManager = new ServiceInstaceManager();
protected void activate(final ComponentContext context) {
this.context = context.getBundleContext();
serviceInstaceManager.activate(this.context);
}
/**
* This indicator checks if --module parameter should be visible or not.
*
* If exists more than one module that match with the properties of
* ModuleFeature APPLICATION, --module parameter should be mandatory.
*
* @param shellContext
* @return
*/
@CliOptionVisibilityIndicator(command = "web mvc setup", params = {"module"},
help = "Module parameter is not available if there is only one application module")
public boolean isModuleVisible(ShellContext shellContext) {
if (getTypeLocationService().getModuleNames(ModuleFeatureName.APPLICATION).size() > 1) {
return true;
}
return false;
}
/**
* This indicator checks if --module parameter should be mandatory or not.
*
* If focused module doesn't match with the properties of ModuleFeature
* APPLICATION, --module parameter should be mandatory.
*
* @param shellContext
* @return
*/
@CliOptionMandatoryIndicator(command = "web mvc setup", params = {"module"})
public boolean isModuleRequired(ShellContext shellContext) {
Pom module = getProjectOperations().getFocusedModule();
if (!isModuleVisible(shellContext)
|| getTypeLocationService().hasModuleFeature(module, ModuleFeatureName.APPLICATION)) {
return false;
}
return true;
}
@CliOptionAutocompleteIndicator(command = "web mvc setup", param = "module",
help = "--module parameter" + " should be autocomplete with an application module.")
public List<String> getAllApplicationModules(ShellContext shellContext) {
return new ArrayList(getTypeLocationService().getModuleNames(ModuleFeatureName.APPLICATION));
}
/**
* This indicator checks if Spring MVC setup is available
*
* If a valid project has been generated and Spring MVC has not been
* installed yet, this command will be available.
*
* @return
*/
@CliAvailabilityIndicator(value = "web mvc setup")
public boolean isSetupAvailable() {
return getControllerOperations().isSetupAvailable();
}
/**
* This method provides the Command definition to be able to include Spring
* MVC on generated project.
*
* @param module
* @param appServer
*/
@CliCommand(value = "web mvc setup",
help = "Includes Spring MVC configuration on generated project. "
+ "Needed for several MVC related commands.")
public void setup(
@CliOption(
key = "module",
mandatory = true,
help = "The application module where to install the Spring MVC support. "
+ "This option is mandatory if the focus is not set in an application module, 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.") Pom module) {
// If module is null and only exists one APPLICATION module, use that by default
boolean usesDefaultModule = false;
if (module == null) {
Collection<Pom> applicationModules =
getTypeLocationService().getModules(ModuleFeatureName.APPLICATION);
if (applicationModules.size() == 1) {
module = applicationModules.iterator().next();
usesDefaultModule = true;
}
}
getControllerOperations().setup(module, usesDefaultModule);
}
/**
* This indicator says if --all parameter should be visible or not
*
* If --entity parameter has been specified, --all parameter will not be
* visible to prevent conflicts.
*
* @return
*/
@CliOptionVisibilityIndicator(params = "all", command = "web mvc controller",
help = "--all parameter is not be 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 should be visible or not
*
* If --all parameter has been specified, --entity parameter will not be
* visible to prevent conflicts.
*
* @return
*/
@CliOptionVisibilityIndicator(params = "entity", command = "web mvc controller",
help = "--entity parameter is not visible if --all parameter has been specified before.")
public boolean isEntityParameterVisible(ShellContext context) {
if (context.getParameters().containsKey("all")) {
return false;
}
return true;
}
/**
* This indicator says if --package, --pathPrefix and --responseType
* parameters should be visible or not
*
* If --all or --entity parameters have not been specified, --package,
* --pathPrefix and --responseType parameters will not be visible.
*
* @return
*/
@CliOptionVisibilityIndicator(
params = {"package", "pathPrefix", "responseType"},
command = "web mvc controller",
help = "--package, --pathPrefix and --responseType parameters are not visible if --all parameter or --entity parameter has not been specified before.")
public boolean areParametersVisibles(ShellContext context) {
if (context.getParameters().containsKey("all") || context.getParameters().containsKey("entity")) {
return true;
}
return false;
}
/**
* Find entities in project and returns a list with their fully qualified
* names.
*
* @param shellContext
* @return List<String> with available entity full qualified names.
*/
@CliOptionAutocompleteIndicator(
command = "web mvc controller",
param = "entity",
help = "--entity parameter must be an existing class annotated with @RooEntity. Please, assign a valid one.")
public List<String> getAllEntities(ShellContext shellContext) {
// Get current value of class
String currentText = shellContext.getParameters().get("entity");
// Create results to return
List<String> results = new ArrayList<String>();
// Get entity full qualified names
Set<ClassOrInterfaceTypeDetails> entities =
getTypeLocationService().findClassesOrInterfaceDetailsWithAnnotation(
RooJavaType.ROO_JPA_ENTITY);
for (ClassOrInterfaceTypeDetails entity : entities) {
if (!entity.isAbstract()) {
String name = getClasspathOperations().replaceTopLevelPackageString(entity, currentText);
if (!results.contains(name)) {
results.add(name);
}
}
}
return results;
}
/**
* This indicator returns all possible values for --responseType parameter.
*
* Depends of the specified --controller, responseTypes will be filtered to
* provide only that responseTypes that doesn't exists on current
* controller. Also, only installed response types will be provided.
*
* @param context
* @return
*/
@CliOptionAutocompleteIndicator(param = "responseType", command = "web mvc controller",
help = "--responseType parameter should be completed with the provided response types.")
public List<String> getAllResponseTypeValues(ShellContext context) {
// Getting all installed services that implements
// ControllerMVCResponseService
Map<String, ControllerMVCResponseService> installedResponseTypes =
getInstalledControllerMVCResponseTypes();
// Generating all possible values
List<String> responseTypes = new ArrayList<String>();
for (Entry<String, ControllerMVCResponseService> responseType : installedResponseTypes
.entrySet()) {
// If specified controller doesn't have this response type
// installed. Add to responseTypes
// possible values
responseTypes.add(responseType.getKey());
}
return responseTypes;
}
/**
* This indicator checks if is possible to add new controllers.
*
* If a valid project has been generated and Spring MVC has been installed,
* this command will be available.
*
* @return
*/
@CliAvailabilityIndicator(value = "web mvc controller")
public boolean isAddControllerAvailable() {
return getControllerOperations().isAddControllerAvailable();
}
/**
* This method provides the Command definition to be able to generate new
* Controllers on current project.
*
* @param all
* @param entity
* @param responseType
* @param package
* @param pathPrefix
*/
@CliCommand(
value = "web mvc controller",
help = "Generates new `@RooController's` in the directory _src/main/java_ of the selected project "
+ "module (if any). The generated controllers should manage specific entities in the project.")
public void addController(
@CliOption(
key = "all",
mandatory = false,
specifiedDefaultValue = "true",
unspecifiedDefaultValue = "false",
help = "Indicates if developer wants to generate controllers 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",
mandatory = false,
help = "The domain entity this controller 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.") JavaType entity,
@CliOption(
key = "responseType",
mandatory = false,
unspecifiedDefaultValue = "JSON",
specifiedDefaultValue = "JSON",
help = "Indicates the responseType to be used by generated controller. Depending on the selected "
+ "responseType, generated methods and views will vary. "
+ "Possible values are: `JSON` plus any response type installed with `web mvc view setup` "
+ "command. "
+ "This option is available once `--all` or `--entity` parameters have been specified. "
+ "Default: `JSON`.") String responseType,
@CliOption(
key = "package",
mandatory = false,
optionContext = APPLICATION_FEATURE,
help = "Indicates which package should be used to include generated controllers. In "
+ "multi-module project you should specify the module name before the package name. "
+ "Ex.: `--package application:org.springframework.roo.web` but, if module name is not "
+ "present, the Roo Shell focused module will be used. "
+ "This option is available only if `--all` or `--entity` option has been specified. "
+ "Default value if not present: `~.web` package, or 'application:~.web' if multi-module "
+ "project.") JavaPackage controllersPackage,
@CliOption(
key = "pathPrefix",
mandatory = false,
specifiedDefaultValue = "",
unspecifiedDefaultValue = "",
help = "Indicates `@RequestMapping` prefix to be used on this controller. It is not necessary "
+ "to specify '/' as Spring Roo shell will include it automatically. "
+ "This option is available only if `--all` or `--entity` option has been specified.") String pathPrefix) {
// Getting --responseType service
Map<String, ControllerMVCResponseService> responseTypeServices =
getInstalledControllerMVCResponseTypes();
// Validate that provided responseType is a valid provided
if (!responseTypeServices.containsKey(responseType)) {
LOGGER
.log(
Level.SEVERE,
"ERROR: Provided responseType is not valid. Use autocomplete feature to obtain valid responseTypes.");
return;
}
pathPrefix = StringUtils.lowerCase(pathPrefix);
// Check --all parameter
if (all) {
getControllerOperations().createOrUpdateControllerForAllEntities(
responseTypeServices.get(responseType), controllersPackage, pathPrefix);
} else {
getControllerOperations().createOrUpdateControllerForEntity(entity,
responseTypeServices.get(responseType), controllersPackage, pathPrefix);
}
}
/**
* This method gets all implementations of ControllerMVCResponseService
* interface to be able to locate all installed ControllerMVCResponseService
*
* @return Map with responseTypes identifier and the
* ControllerMVCResponseService implementation
*/
public Map<String, ControllerMVCResponseService> getInstalledControllerMVCResponseTypes() {
try {
ServiceReference<?>[] references =
this.context.getAllServiceReferences(ControllerMVCResponseService.class.getName(), null);
for (ServiceReference<?> ref : references) {
ControllerMVCResponseService responseTypeService =
(ControllerMVCResponseService) this.context.getService(ref);
boolean isInstalled = false;
for (Pom module : getProjectOperations().getPoms()) {
if (responseTypeService.isInstalledInModule(module.getModuleName())) {
isInstalled = true;
break;
}
}
if (isInstalled) {
responseTypes.put(responseTypeService.getResponseType(), responseTypeService);
}
}
return responseTypes;
} catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load ControllerMVCResponseService on ControllerCommands.");
return null;
}
}
// Detail commands
/**
* This indicator checks if is possible to add new detail controllers.
*
* If a valid project has been generated and Spring MVC has been installed,
* this command will be available.
*
* @return
*/
@CliAvailabilityIndicator(value = "web mvc detail")
public boolean isAddDetailControllerAvailable() {
return getControllerOperations().isAddDetailControllerAvailable();
}
@CliOptionVisibilityIndicator(params = "all", command = "web mvc detail",
help = "--all parameter is not be visible if --entity parameter has been specified before.")
public boolean isAllParameterOfDetailCommandVisible(ShellContext context) {
if (context.getParameters().containsKey("entity")) {
return false;
}
return true;
}
@CliOptionVisibilityIndicator(params = "entity", command = "web mvc detail",
help = "--entity parameter is not be visible if --all parameter has been specified before.")
public boolean isEntityParameterOfDetailCommandVisible(ShellContext context) {
if (context.getParameters().containsKey("all")) {
return false;
}
return true;
}
@CliOptionAutocompleteIndicator(
command = "web mvc detail",
param = "entity",
help = "--entity parameter must be an existing class annotated with @RooJpaEntity. Please, assign a valid one.")
public List<String> getAllEntitiesForDetailCommands(ShellContext shellContext) {
// Get current value of class
String currentText = shellContext.getParameters().get("entity");
// Create results to return
List<String> results = new ArrayList<String>();
// Get entity full qualified names
Set<ClassOrInterfaceTypeDetails> entities =
getTypeLocationService().findClassesOrInterfaceDetailsWithAnnotation(
RooJavaType.ROO_JPA_ENTITY);
for (ClassOrInterfaceTypeDetails entity : entities) {
String name = getClasspathOperations().replaceTopLevelPackageString(entity, currentText);
if (!results.contains(name)) {
results.add(name);
}
}
return results;
}
@CliOptionVisibilityIndicator(params = {"field"}, command = "web mvc detail",
help = "--field parameter is visible if --entity parameter has been specified before.")
public boolean isFieldParameterVisible(ShellContext context) {
if (context.getParameters().containsKey("entity")) {
return true;
}
return false;
}
@CliOptionAutocompleteIndicator(
command = "web mvc detail",
param = "field",
help = "--field parameter must be an existing @OneToMany or @ManyToMany field of the specified entity in parameter --entity.",
includeSpaceOnFinish = false)
public List<String> getDetailFieldsRelatedToEntity(ShellContext shellContext) {
// Get current value of class
String currentEntity = shellContext.getParameters().get("entity");
// Get current fields in --field value
String currentFieldValue = shellContext.getParameters().get("field");
// Check the field value (ex: 'entity.detailentity.')
String[] splittedCurrentField = null;
boolean includeChildren = false;
boolean removedData = false;
if (currentFieldValue.contains(".")) {
if (!currentFieldValue.endsWith(".")) {
currentFieldValue = currentFieldValue.substring(0, currentFieldValue.lastIndexOf("."));
removedData = true;
}
includeChildren = true;
splittedCurrentField = currentFieldValue.split("[.]");
} else {
currentFieldValue = "";
}
// Create results to return
List<String> results = new ArrayList<String>();
// Get entity full qualified names
Set<ClassOrInterfaceTypeDetails> entities =
getTypeLocationService().findClassesOrInterfaceDetailsWithAnnotation(
RooJavaType.ROO_JPA_ENTITY);
for (ClassOrInterfaceTypeDetails entity : entities) {
if (getClasspathOperations().replaceTopLevelPackageString(entity,
entity.getType().getModule()).equals(currentEntity)) {
if (includeChildren) {
// Get the entity where search the fields
entity = getEntityByDetailField(entity, splittedCurrentField, 0);
}
if (removedData) {
currentFieldValue = currentFieldValue.concat(".");
}
results.addAll(getDetailFieldsRelatedToEntity(entity, currentFieldValue));
}
}
return results;
}
@CliOptionAutocompleteIndicator(param = "responseType", command = "web mvc detail",
help = "--responseType parameter should be completed with an installed response type.")
public List<String> getAllResponseTypeForDetailCommandValues(ShellContext context) {
// Getting all installed services that implements
// ControllerMVCResponseService
Map<String, ControllerMVCResponseService> installedResponseTypes =
getInstalledControllerMVCResponseTypes();
// Generating all possible values
List<String> responseTypes = new ArrayList<String>();
for (Entry<String, ControllerMVCResponseService> responseType : installedResponseTypes
.entrySet()) {
// If specified controller doesn't have this response type
// installed. Add to responseTypes
// possible values
responseTypes.add(responseType.getKey());
}
return responseTypes;
}
@CliOptionVisibilityIndicator(
params = {"package", "responseType"},
command = "web mvc detail",
help = "--package, --pathPrefix and --responseType parameters are not visible until --all parameter or --entity parameter has been specified.")
public boolean areDetailParametersVisibles(ShellContext context) {
if (context.getParameters().containsKey("all") || context.getParameters().containsKey("entity")) {
return true;
}
return false;
}
@CliOptionVisibilityIndicator(params = {"views"}, command = "web mvc detail",
help = "--views parameter is not visible until a valid --responseType has specified.")
public boolean isViewParameterAvailable(ShellContext context) {
if (context.getParameters().containsKey("responseType")) {
// Get the specified one
String currentResponseType = context.getParameters().get("responseType");
// Check the responseType
Map<String, ControllerMVCResponseService> responseTypeServices =
getInstalledControllerMVCResponseTypes();
for (Entry<String, ControllerMVCResponseService> responseTypeService : responseTypeServices
.entrySet()) {
if (responseTypeService.getKey().equals(currentResponseType)
&& responseTypeService.getValue().providesViews()) {
return true;
}
}
}
return false;
}
@CliOptionAutocompleteIndicator(
command = "web mvc detail",
param = "views",
includeSpaceOnFinish = false,
help = "--views parameter could be autocomplete with a separated comma list including 'list' and 'show'. If --entity parameter "
+ "has been specified, is possible to autocomplete with existing finder views too.")
public List<String> getAllAvailableViews(ShellContext shellContext) {
List<String> viewsValuesToReturn = new ArrayList<String>();
// Get current views in --views value
String currentViewsValue = shellContext.getParameters().get("views");
String[] views = StringUtils.split(currentViewsValue, ",");
// Check for bad written separators and return no options
if (currentViewsValue.contains(",.") || currentViewsValue.contains(".,")) {
return viewsValuesToReturn;
}
// Check if --entity parameter has been specified
JavaType currentEntity = getTypeFromEntityParam(shellContext);
List<String> finderViews = new ArrayList<String>();
if (currentEntity != null) {
// Obtain search controllers for the current entity
Collection<ClassOrInterfaceTypeDetails> searchControllers =
getControllerLocator().getControllers(currentEntity, ControllerType.SEARCH,
RooJavaType.ROO_THYMELEAF);
for (ClassOrInterfaceTypeDetails searchController : searchControllers) {
SearchAnnotationValues searchAnnotationValues =
new SearchAnnotationValues(searchController);
if (searchAnnotationValues.getFinders() != null
&& searchAnnotationValues.getFinders().length > 0) {
finderViews.addAll(Arrays.asList(searchAnnotationValues.getFinders()));
}
}
}
// Check if it is first view
if (currentViewsValue.equals("")) {
viewsValuesToReturn.add("list");
viewsValuesToReturn.add("show");
viewsValuesToReturn.addAll(finderViews);
} else if (currentViewsValue.endsWith(",")) {
String finishedViews = "";
for (int i = 0; i < views.length; i++) {
finishedViews += views[i] + ",";
}
if (!finishedViews.contains("list,")) {
viewsValuesToReturn.add(finishedViews + "list");
}
if (!finishedViews.contains("show,")) {
viewsValuesToReturn.add(finishedViews + "show");
}
for (String finderView : finderViews) {
if (!finishedViews.contains(finderView + ",")) {
viewsValuesToReturn.add(finishedViews + finderView);
}
}
} else if (views.length == 1) {
viewsValuesToReturn.add("list");
viewsValuesToReturn.add("show");
viewsValuesToReturn.addAll(finderViews);
} else {
String finishedViews = "";
for (int i = 0; i < views.length - 1; i++) {
finishedViews += views[i] + ",";
}
if (!finishedViews.contains("list,")) {
viewsValuesToReturn.add(finishedViews + "list");
}
if (!finishedViews.contains("show,")) {
viewsValuesToReturn.add(finishedViews + "show");
}
for (String finderView : finderViews) {
if (!finishedViews.contains(finderView + ",")) {
viewsValuesToReturn.add(finishedViews + finderView);
}
}
}
return viewsValuesToReturn;
}
/**
* This method provides the Command definition to be able to generate new
* Controllers on current project.
*
* @param all
* @param entity
* @param responseType
* @param package
* @param pathPrefix
*/
@CliCommand(
value = "web mvc detail",
help = "Generates new `@RooController` for relation fields which detail wants to be managed. "
+ "It must be a `@OneToMany` field. Generated controllers will have `@RooDetail` with info "
+ "about the parent entity and the parent views where the detail will be displayed.")
public void addDetailController(
@CliOption(
key = "all",
mandatory = false,
specifiedDefaultValue = "true",
unspecifiedDefaultValue = "false",
help = "Indicates if developer wants to generate detail controllers for each `@OneToMany` "
+ "relation of field in each entity in the 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",
mandatory = false,
help = "Indicates the entity which this detail controller manages. 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.") JavaType entity,
@CliOption(
key = "field",
mandatory = false,
specifiedDefaultValue = "",
unspecifiedDefaultValue = "",
help = "Indicates the entity's field on which the detail controller is generated. It must be "
+ "a `@OneToMany` field. "
+ "Possible values are: fields representing a `@OneToMany` relation of the entity specified"
+ " in `--entity` parameter. "
+ "This param is only available if `--entity` parameter has been specified before.") String field,
@CliOption(
key = "package",
mandatory = false,
optionContext = APPLICATION_FEATURE,
help = "Indicates the Java package where the detail controllers should be generated. In"
+ " multi-module project you should specify the module name before the package name. "
+ "Ex.: `--package application:org.springframework.roo.web` but, if module name is not "
+ "present, the Roo Shell focused module will be used. "
+ "This option is available only if `--all` or `--entity` option has been specified. "
+ "Default if option not present: `~.web` package, or 'application:~.web' if multi-module "
+ "project.") JavaPackage controllersPackage,
@CliOption(
key = "responseType",
mandatory = false,
unspecifiedDefaultValue = "JSON",
specifiedDefaultValue = "JSON",
help = "Indicates the responseType to be used by generated detail controllers. Depending on "
+ "the selected responseType, generated methods and views will vary. "
+ "Possible values are: `JSON` plus any response type installed with `web mvc view setup` "
+ "command. "
+ "This option is available once `--all` or `--entity` parameters have been specified. "
+ "Default: `JSON`.") String responseType,
@CliOption(
key = "views",
mandatory = false,
specifiedDefaultValue = "list",
help = "Separated comma list where developer could specify the different parent views where "
+ "this new detail will be displayed. "
+ "This parameter is not available if the provided `--responseType` doesn't use views to display "
+ "the data. "
+ "Possible values are: 'list', 'show' or the different parent finder views (if exists). "
+ "Default if option not present: The parent 'list' view if it exists.") String viewsList) {
// Getting --responseType service
Map<String, ControllerMVCResponseService> responseTypeServices =
getInstalledControllerMVCResponseTypes();
// Validate that provided responseType is a valid provided
if (!responseTypeServices.containsKey(responseType)) {
LOGGER
.log(
Level.SEVERE,
"ERROR: Provided responseType is not valid. Use autocomplete feature to obtain valid responseTypes.");
return;
}
// Check --all parameter
if (all) {
getControllerOperations().createOrUpdateDetailControllersForAllEntities(
responseTypeServices.get(responseType), controllersPackage, viewsList);
} else {
getControllerOperations().createOrUpdateDetailControllerForEntity(entity, field,
responseTypeServices.get(responseType), controllersPackage, viewsList);
}
}
/**
* Get a entity by field recursively
*
* @param paternEntity
* Root entity
* @param field
* Field to search the entity
* @param level
* Current recursion level
* @return
*/
private ClassOrInterfaceTypeDetails getEntityByDetailField(
ClassOrInterfaceTypeDetails paternEntity, String field[], int level) {
ClassOrInterfaceTypeDetails entity = paternEntity;
MemberDetails entityDetails =
getMemberDetailsScanner().getMemberDetails(paternEntity.getType().getSimpleTypeName(),
paternEntity);
List<FieldMetadata> fields = entityDetails.getFields();
for (FieldMetadata entityField : fields) {
if (entityField.getFieldName().getSymbolName().equals(field[level])) {
entity =
getTypeLocationService().getTypeDetails(
entityField.getFieldType().getParameters().get(0));
}
}
level++;
if (level < field.length) {
entity = getEntityByDetailField(entity, field, level);
}
return entity;
}
/**
* Get a field list that can be selected to do a detail controller.
*
* @param entity
* Entity on which create the detail controller
* @param parentFieldName
* The parent's field name used to construct the field name
* related with the original entity
* @return the related field list
*/
private List<String> getDetailFieldsRelatedToEntity(ClassOrInterfaceTypeDetails entity,
String parentFieldName) {
List<String> results = new ArrayList<String>();
MemberDetails entityDetails =
getMemberDetailsScanner().getMemberDetails(entity.getType().getSimpleTypeName(), entity);
List<FieldMetadata> fields = entityDetails.getFields();
for (FieldMetadata field : fields) {
AnnotationMetadata oneToManyAnnotation = field.getAnnotation(JpaJavaType.ONE_TO_MANY);
AnnotationMetadata manyToManyAnnotation = field.getAnnotation(JpaJavaType.MANY_TO_MANY);
if ((oneToManyAnnotation != null || manyToManyAnnotation != null)
&& (field.getFieldType().getFullyQualifiedTypeName()
.equals(JavaType.LIST.getFullyQualifiedTypeName()) || field.getFieldType()
.getFullyQualifiedTypeName().equals(JavaType.SET.getFullyQualifiedTypeName()))) {
results.add(parentFieldName.concat(field.getFieldName().getSymbolName()));
}
}
return results;
}
// operation command
@CliOptionAutocompleteIndicator(
param = "controller",
command = "web mvc operation",
help = "--controller parameter should be completed with an controller generated previously or with a name that will be used to create a new controller.")
public List<String> getAllControllerForOperationCommandValues(ShellContext context) {
// Get the actual controller selected
String currentText = context.getParameters().get("controller");
// Create results to return
List<String> results = new ArrayList<String>();
// Get controllers full qualified names
Set<ClassOrInterfaceTypeDetails> controllers =
getTypeLocationService().findClassesOrInterfaceDetailsWithAnnotation(
RooJavaType.ROO_CONTROLLER);
for (ClassOrInterfaceTypeDetails controller : controllers) {
String name = getClasspathOperations().replaceTopLevelPackageString(controller, currentText);
if (!results.contains(name)) {
results.add(name);
}
}
return results;
}
@CliOptionAutocompleteIndicator(param = "service", command = "web mvc operation",
help = "--service parameter should be completed with an service generated previously.")
public List<String> getAllServiceForOperationCommandValues(ShellContext context) {
// Get the actual service selected
String currentText = context.getParameters().get("service");
// Create results to return
List<String> results = new ArrayList<String>();
// Get controllers full qualified names
Set<ClassOrInterfaceTypeDetails> services =
getTypeLocationService().findClassesOrInterfaceDetailsWithAnnotation(
RooJavaType.ROO_SERVICE);
for (ClassOrInterfaceTypeDetails service : services) {
String name = getClasspathOperations().replaceTopLevelPackageString(service, currentText);
if (!results.contains(name)) {
results.add(name);
}
}
return results;
}
@CliOptionAutocompleteIndicator(param = "operation", command = "web mvc operation",
help = "--operation parameter should be completed with the operations availables to publish.")
public List<String> getAllOperationsForOperationCommandValues(ShellContext context) {
// Getting the service
String currentService = context.getParameters().get("service");
// Get the controller and try to get the related service
String currentController = context.getParameters().get("controller");
return getControllerOperations().getAllMethodsToPublish(currentService, currentController);
}
@CliOptionVisibilityIndicator(params = {"all"}, command = "web mvc operation",
help = "--all parameter isn't visible if --operation parameter has been specified before.")
public boolean isAllParameterOperationVisible(ShellContext context) {
if (context.getParameters().containsKey("operation")) {
return false;
}
return true;
}
@CliOptionVisibilityIndicator(params = {"operation"}, command = "web mvc operation",
help = "--operation parameter isn't visible if --all parameter has been specified before.")
public boolean isOperationParameterOperationVisible(ShellContext context) {
if (context.getParameters().containsKey("all")) {
return false;
}
return true;
}
@CliOptionVisibilityIndicator(params = {"service"}, command = "web mvc operation",
help = "--service parameter isn't visible if --operation parameter has been specified.")
public boolean isServiceParameterOperationVisible(ShellContext context) {
// Check if parameter --service has been specified
if (context.getParameters().containsKey("service")) {
return true;
}
// Check if controller exists and if parameter --operation has been specified
if (context.getParameters().containsKey("operation")) {
return false;
}
return true;
}
/**
* This indicator checks if is possible to publish own operations in
* controllers.
*
* If a valid project has been generated and Spring MVC has been installed,
* this command will be available.
*
* @return
*/
@CliAvailabilityIndicator(value = "web mvc operation")
public boolean isPublishOperationsAvailable() {
return getControllerOperations().isPublishOperationsAvailable();
}
/**
* This method provides the Command definition to be able to publish service
* operations in controllers.
*
* @param controller
* @param service
* @param operation
* @param all
*/
/* TODO: TO BE IMPLEMENTED
@CliCommand(value = "web mvc operation",
help = "Update or generates @RooController with service operations")
public void addOperationInController(
@CliOption(key = "controller", mandatory = true,
help = "Indicates the controller where should be published the service methods") JavaType controller,
@CliOption(
key = "service",
mandatory = false,
help = "This param will be visible if --controller parameter doesn't exists in the application. Indicates the service on which methods should be published in the controller") JavaType service,
@CliOption(
key = "operation",
mandatory = false,
help = "This param will be visible if --all parameter hasn't been specified. Indicates the operation of the service that should be published in the controller") String operation,
@CliOption(
key = "all",
mandatory = false,
specifiedDefaultValue = "true",
unspecifiedDefaultValue = "false",
help = "This param will be visible if --operation parameter hasn't been specified. Indicates if every method of the service should be published in the controller") boolean all) {
List<String> operations = new ArrayList<String>();
// Check --all parameter
if (all) {
// Getting the service
String currentService = null;
String serviceName = "";
if (service != null) {
currentService = service.getFullyQualifiedTypeName();
// Get service name to concatenate it on each method name
serviceName =
getControllerOperations().replaceTopLevelPackage(
getTypeLocationService().getTypeDetails(service));
}
// Getting the controller
String currentController = null;
if (controller != null) {
currentController = controller.getFullyQualifiedTypeName();
}
List<String> allMethodsToPublish =
getControllerOperations().getAllMethodsToPublish(currentService, currentController);
for (String methodToPublish : allMethodsToPublish) {
if (service != null) {
methodToPublish = serviceName.concat(".").concat(methodToPublish);
}
operations.add(methodToPublish);
}
// Get all the methods related with controller or service
} else {
if (service != null) {
// Add service name to operation
String serviceName =
getControllerOperations().replaceTopLevelPackage(
getTypeLocationService().getTypeDetails(service));
operation = serviceName.concat(".").concat(operation);
}
operations.add(operation);
}
getControllerOperations().exportOperation(controller, operations);
}*/
/**
* Tries to obtain JavaType indicated in command or which has the focus in the
* Shell
*
* @param shellContext the Roo Shell context
* @return JavaType or null if no class has the focus or no class is specified
* in the command
*/
private JavaType getTypeFromEntityParam(ShellContext shellContext) {
// Try to get 'class' from ShellContext
String typeString = shellContext.getParameters().get("entity");
JavaType type = null;
if (typeString != null) {
JavaTypeConverter converter = (JavaTypeConverter) getJavaTypeConverter().get(0);
type = converter.convertFromText(typeString, JavaType.class, PROJECT);
}
return type;
}
// Gets OSGi Services
public ControllerOperations getControllerOperations() {
return serviceInstaceManager.getServiceInstance(this, ControllerOperations.class);
}
public ProjectOperations getProjectOperations() {
return serviceInstaceManager.getServiceInstance(this, ProjectOperations.class);
}
public PathResolver getPathResolver() {
return serviceInstaceManager.getServiceInstance(this, PathResolver.class);
}
public FileManager getFileManager() {
return serviceInstaceManager.getServiceInstance(this, FileManager.class);
}
public TypeLocationService getTypeLocationService() {
return serviceInstaceManager.getServiceInstance(this, TypeLocationService.class);
}
public ClasspathOperations getClasspathOperations() {
return serviceInstaceManager.getServiceInstance(this, ClasspathOperations.class);
}
public MemberDetailsScanner getMemberDetailsScanner() {
return serviceInstaceManager.getServiceInstance(this, MemberDetailsScanner.class);
}
public ControllerLocator getControllerLocator() {
return serviceInstaceManager.getServiceInstance(this, ControllerLocator.class);
}
@SuppressWarnings("unchecked")
public List<Converter> getJavaTypeConverter() {
return serviceInstaceManager.getServiceInstance(this, Converter.class,
new ServiceInstaceManager.Matcher<Converter>() {
@Override
public boolean match(Converter service) {
if (service instanceof JavaTypeConverter) {
return true;
}
return false;
}
});
}
}