package org.springframework.roo.addon.layers.service.addon;
import static java.lang.reflect.Modifier.PUBLIC;
import static org.springframework.roo.model.RooJavaType.ROO_JPA_ENTITY;
import static org.springframework.roo.model.RooJavaType.ROO_SERVICE;
import static org.springframework.roo.model.RooJavaType.ROO_SERVICE_IMPL;
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.addon.layers.repository.jpa.addon.RepositoryJpaLocator;
import org.springframework.roo.classpath.PhysicalTypeCategory;
import org.springframework.roo.classpath.PhysicalTypeIdentifier;
import org.springframework.roo.classpath.TypeLocationService;
import org.springframework.roo.classpath.TypeManagementService;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetailsBuilder;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
import org.springframework.roo.classpath.details.annotations.ClassAttributeValue;
import org.springframework.roo.model.JavaPackage;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.model.RooJavaType;
import org.springframework.roo.process.manager.FileManager;
import org.springframework.roo.project.Dependency;
import org.springframework.roo.project.DependencyScope;
import org.springframework.roo.project.DependencyType;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.PathResolver;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.support.logging.HandlerUtils;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Class that implements {@link ServiceOperations}.
*
* @author Stefan Schmidt
* @author Juan Carlos GarcĂa
* @since 1.2.0
*/
@Component
@Service
public class ServiceOperationsImpl implements ServiceOperations {
private static final Logger LOGGER = HandlerUtils.getLogger(ServiceOperationsImpl.class);
@Reference
private FileManager fileManager;
@Reference
private PathResolver pathResolver;
@Reference
private ProjectOperations projectOperations;
@Reference
private TypeManagementService typeManagementService;
@Reference
private TypeLocationService typeLocationService;
@Reference
private RepositoryJpaLocator repositoryJpaLocator;
@Override
public boolean areServiceCommandsAvailable() {
return projectOperations.isFocusedProjectAvailable();
}
@Override
public void addAllServices(JavaPackage apiPackage, JavaPackage implPackage) {
Validate.notNull(apiPackage.getModule(), "ApiPackage module is required");
Validate.notNull(implPackage.getModule(), "ImplPackage module is required");
// Getting all generated entities
Set<ClassOrInterfaceTypeDetails> entities =
typeLocationService.findClassesOrInterfaceDetailsWithAnnotation(ROO_JPA_ENTITY);
for (final ClassOrInterfaceTypeDetails domainType : entities) {
// Ignore abstract entities or entities without repository
if (!domainType.isAbstract()) {
// Get repository
ClassOrInterfaceTypeDetails repository =
repositoryJpaLocator.getRepository(domainType.getName());
if (repository != null) {
// Creating service interfaces for every entity
JavaType interfaceType =
new JavaType(String.format("%s.%sService", apiPackage.getFullyQualifiedPackageName(),
domainType.getName().getSimpleTypeName()), apiPackage.getModule());
// Creating service implementation for every entity
JavaType implType =
new JavaType(String.format("%s.%sServiceImpl", implPackage
.getFullyQualifiedPackageName(), domainType.getName().getSimpleTypeName()),
implPackage.getModule());
// Delegates on individual service creator
addService(domainType.getType(), repository.getName(), interfaceType, implType);
}
}
}
}
/**
* Informs if this component can generate service class for required entity
*
* @param domainType
* @return
*/
public boolean canGenerateService(JavaType domainType) {
return canGenerateService(typeLocationService.getTypeDetails(domainType));
}
/**
* Informs if this component can generate service class for required entity
*
* @param domainType
* @return
*/
public boolean canGenerateService(ClassOrInterfaceTypeDetails domainType) {
ClassOrInterfaceTypeDetails repository =
repositoryJpaLocator.getFirstRepository(domainType.getName());
if (repository == null) {
return false;
}
return true;
}
@Override
public void addService(final JavaType domainType, final JavaType interfaceType,
final JavaType implType) {
// Find repository related to entity
ClassOrInterfaceTypeDetails repository = repositoryJpaLocator.getRepository(domainType);
if (repository == null) {
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.",
domainType.getSimpleTypeName()));
return;
}
addService(domainType, repository.getName(), interfaceType, implType);
}
@Override
public void addService(final JavaType domainType, JavaType repositoryType,
JavaType interfaceType, JavaType implType) {
Validate.notNull(domainType, "ERROR: Domain type required to be able to generate service.");
if (projectOperations.isMultimoduleProject()) {
Validate
.notNull(repositoryType,
"ERROR: You must specify a repository type to be able to generate service on multimodule projects.");
Validate
.notNull(interfaceType,
"ERROR: You must specify a interface type to be able to generate service on multimodule projects.");
}
// Check if current entity is related with the repository
ClassOrInterfaceTypeDetails repository = null;
if (repositoryType == null) {
repository = repositoryJpaLocator.getFirstRepository(domainType);
if (repository == null) {
// Is necessary at least one repository to generate service
LOGGER
.log(
Level.INFO,
String
.format(
"ERROR: You must generate a repository to '%s' entity before to generate a new service.",
domainType.getFullyQualifiedTypeName()));
return;
}
repositoryType = repository.getName();
} else {
repository = typeLocationService.getTypeDetails(repositoryType);
}
if (interfaceType == null) {
interfaceType =
new JavaType(String.format("%s.service.api.%s%s",
projectOperations.getFocusedTopLevelPackage(), domainType.getSimpleTypeName(),
implType == null ? "Service" : implType.getSimpleTypeName().concat("Api")), "");
}
if (implType == null) {
implType =
new JavaType(String.format("%s.service.impl.%sServiceImpl",
projectOperations.getFocusedTopLevelPackage(), domainType.getSimpleTypeName()),
interfaceType.getModule());
}
// Generating service interface
createServiceInterface(domainType, interfaceType);
// Generating service implementation
createServiceImplementation(interfaceType, implType, repository, domainType);
}
/**
* Method that creates the service interface
*
* @param domainType
* @param interfaceType
*/
private void createServiceInterface(final JavaType domainType, final JavaType interfaceType) {
Validate.notNull(interfaceType.getModule(), "JavaType %s does not have a module", domainType);
// Checks if new service interface already exists.
final String interfaceIdentifier =
pathResolver.getCanonicalPath(interfaceType.getModule(), Path.SRC_MAIN_JAVA, interfaceType);
if (fileManager.exists(interfaceIdentifier)) {
return; // Type already exists - nothing to do
}
// Validate that user provides a valid entity
Validate.notNull(domainType, "ERROR: Domain type required to generate service");
ClassOrInterfaceTypeDetails entityDetails = typeLocationService.getTypeDetails(domainType);
Validate.notNull(entityDetails.getAnnotation(RooJavaType.ROO_JPA_ENTITY),
"ERROR: Provided entity should be annotated with @RooJpaEntity");
// Generating @RooService annotation
final AnnotationMetadataBuilder interfaceAnnotationMetadata =
new AnnotationMetadataBuilder(ROO_SERVICE);
interfaceAnnotationMetadata.addAttribute(new ClassAttributeValue(new JavaSymbolName("entity"),
domainType));
// Creating interface builder
final String interfaceMid =
PhysicalTypeIdentifier.createIdentifier(interfaceType,
pathResolver.getPath(interfaceIdentifier));
final ClassOrInterfaceTypeDetailsBuilder interfaceTypeBuilder =
new ClassOrInterfaceTypeDetailsBuilder(interfaceMid, PUBLIC, interfaceType,
PhysicalTypeCategory.INTERFACE);
// Adding @RooService annotation to current interface
interfaceTypeBuilder.addAnnotation(interfaceAnnotationMetadata.build());
// Write service interface on disk
typeManagementService.createOrUpdateTypeOnDisk(interfaceTypeBuilder.build());
// Add dependencies between modules
projectOperations.addModuleDependency(interfaceType.getModule(), domainType.getModule());
// Add springlets-data-commons dependency
projectOperations.addDependency(interfaceType.getModule(), new Dependency("io.springlets",
"springlets-data-commons", null));
}
/**
* Method that creates the service implementation
*
* @param interfaceType
* @param implType
* @param domainType
*/
private void createServiceImplementation(final JavaType interfaceType, JavaType implType,
ClassOrInterfaceTypeDetails repository, JavaType domainType) {
Validate.notNull(interfaceType,
"ERROR: Interface should be provided to be able to generate its implementation");
Validate.notNull(interfaceType.getModule(), "ERROR: Interface module is required");
Validate.notNull(domainType, "ERROR: Domain type required to generate service");
// Generating implementation JavaType if needed
if (implType == null) {
implType =
new JavaType(String.format("%sImpl", interfaceType.getFullyQualifiedTypeName()),
interfaceType.getModule());
}
Validate.notNull(implType.getModule(), "ERROR: Implementation module is required");
// Checks if new service interface already exists.
final String implIdentifier =
pathResolver.getCanonicalPath(implType.getModule(), Path.SRC_MAIN_JAVA, implType);
if (fileManager.exists(implIdentifier)) {
return; // Type already exists - nothing to do
}
// Generating @RooServiceImpl annotation
final AnnotationMetadataBuilder implAnnotationMetadata =
new AnnotationMetadataBuilder(ROO_SERVICE_IMPL);
implAnnotationMetadata.addAttribute(new ClassAttributeValue(new JavaSymbolName("service"),
interfaceType));
// Creating class builder
final String implMid =
PhysicalTypeIdentifier.createIdentifier(implType, pathResolver.getPath(implIdentifier));
final ClassOrInterfaceTypeDetailsBuilder implTypeBuilder =
new ClassOrInterfaceTypeDetailsBuilder(implMid, PUBLIC, implType,
PhysicalTypeCategory.CLASS);
// Adding @RooService annotation to current interface
implTypeBuilder.addAnnotation(implAnnotationMetadata.build());
// Adding implements
implTypeBuilder.addImplementsType(interfaceType);
final String declaredByMetadataId =
PhysicalTypeIdentifier.createIdentifier(implTypeBuilder.build().getType(),
pathResolver.getPath(implType.getModule(), Path.SRC_MAIN_JAVA));
// Write service implementation on disk
typeManagementService.createOrUpdateTypeOnDisk(implTypeBuilder.build());
// Add dependencies between modules
projectOperations.addModuleDependency(implType.getModule(), interfaceType.getModule());
projectOperations.addModuleDependency(implType.getModule(), repository.getName().getModule());
projectOperations.addModuleDependency(implType.getModule(), domainType.getModule());
// ROO-3799 Included dependency spring-tx if it's a multimodule project
if (projectOperations.isMultimoduleProject()) {
projectOperations.addDependency(implType.getModule(), new Dependency("org.springframework",
"spring-tx", "", DependencyType.JAR, DependencyScope.COMPILE));
}
}
}