package org.springframework.roo.addon.web.mvc.views;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Logger;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.felix.scr.annotations.Component;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.ComponentContext;
import org.springframework.roo.addon.dto.addon.EntityProjectionLocator;
import org.springframework.roo.addon.jpa.addon.entity.JpaEntityMetadata;
import org.springframework.roo.addon.jpa.addon.entity.JpaEntityMetadata.RelationInfo;
import org.springframework.roo.addon.jpa.annotations.entity.JpaRelationType;
import org.springframework.roo.addon.layers.repository.jpa.addon.RepositoryJpaLocator;
import org.springframework.roo.addon.layers.repository.jpa.addon.RepositoryJpaMetadata;
import org.springframework.roo.addon.plural.addon.PluralService;
import org.springframework.roo.addon.web.mvc.controller.addon.ControllerAnnotationValues;
import org.springframework.roo.addon.web.mvc.controller.addon.ControllerLocator;
import org.springframework.roo.addon.web.mvc.controller.addon.ControllerMetadata;
import org.springframework.roo.addon.web.mvc.controller.addon.ControllerOperations;
import org.springframework.roo.addon.web.mvc.controller.addon.RelationInfoExtended;
import org.springframework.roo.addon.web.mvc.controller.annotations.ControllerType;
import org.springframework.roo.addon.web.mvc.i18n.I18nOperations;
import org.springframework.roo.addon.web.mvc.i18n.components.I18n;
import org.springframework.roo.addon.web.mvc.views.components.DetailEntityItem;
import org.springframework.roo.addon.web.mvc.views.components.EntityItem;
import org.springframework.roo.addon.web.mvc.views.components.FieldItem;
import org.springframework.roo.addon.web.mvc.views.components.FieldTypes;
import org.springframework.roo.addon.web.mvc.views.components.MenuEntry;
import org.springframework.roo.classpath.PhysicalTypeCategory;
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.AnnotationAttributeValue;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.details.annotations.StringAttributeValue;
import org.springframework.roo.classpath.operations.Cardinality;
import org.springframework.roo.classpath.persistence.PersistenceMemberLocator;
import org.springframework.roo.classpath.scanner.MemberDetails;
import org.springframework.roo.classpath.scanner.MemberDetailsScanner;
import org.springframework.roo.metadata.MetadataService;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.model.JdkJavaType;
import org.springframework.roo.model.JpaJavaType;
import org.springframework.roo.model.Jsr303JavaType;
import org.springframework.roo.model.RooJavaType;
import org.springframework.roo.model.SpringJavaType;
import org.springframework.roo.model.SpringletsJavaType;
import org.springframework.roo.process.manager.FileManager;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.support.logging.HandlerUtils;
import org.springframework.roo.support.osgi.ServiceInstaceManager;
import org.springframework.roo.support.util.XmlUtils;
/**
* This abstract class implements MVCViewGenerationService interface that
* provides all necessary elements to generate views inside project.
*
* @param <DOC>
* @param <T>
* @author Juan Carlos GarcÃa
* @author Sergio Clares
* @since 2.0
*/
@Component(componentAbstract = true)
public abstract class AbstractViewGenerationService<DOC, T extends AbstractViewMetadata> implements
MVCViewGenerationService<T> {
// Max fields that will be included on generated view
private static final int MAX_FIELDS_TO_ADD = 5;
private final List<JavaType> STANDAR_TYPES = Arrays.asList(JavaType.BOOLEAN_OBJECT,
JavaType.STRING, JavaType.LONG_OBJECT, JavaType.INT_OBJECT, JavaType.FLOAT_OBJECT,
JavaType.DOUBLE_OBJECT);
private final List<JavaType> DATE_TIME_TYPES = Arrays.asList(JdkJavaType.DATE,
JdkJavaType.CALENDAR);
private static Logger LOGGER = HandlerUtils.getLogger(AbstractViewGenerationService.class);
private ServiceInstaceManager serviceInstaceManager = new ServiceInstaceManager();
// ------------ OSGi component attributes ----------------
protected BundleContext context;
protected void activate(final ComponentContext context) {
this.context = context.getBundleContext();
serviceInstaceManager.activate(this.context);
}
protected abstract DOC process(String templateName, ViewContext<T> ctx);
protected abstract DOC parse(String content);
protected abstract DOC merge(String templateName, DOC loadExistingDoc, ViewContext<T> ctx);
protected abstract DOC merge(String templateName, DOC existingDoc, ViewContext<T> ctx,
List<FieldItem> fields);
protected abstract DOC mergeListView(String templateName, DOC loadExistingDoc,
ViewContext<T> ctx, EntityItem entity, List<FieldItem> fields,
List<List<DetailEntityItem>> detailsLevels);
protected abstract DOC mergeListDeleteModalView(String templateName, DOC loadExistingDoc,
ViewContext<T> ctx, EntityItem entity, List<FieldItem> fields);
protected abstract DOC mergeListDeleteModalDetailView(String templateName, DOC loadExistingDoc,
ViewContext<T> ctx, EntityItem entity, List<FieldItem> fields);
protected abstract DOC mergeListDeleteModalBatchView(String templateName, DOC loadExistingDoc,
ViewContext<T> ctx, EntityItem entity, List<FieldItem> fields);
protected abstract DOC mergeListDeleteModalBatchDetailView(String templateName,
DOC loadExistingDoc, ViewContext<T> ctx, EntityItem entity, List<FieldItem> fields);
protected abstract DOC mergeMenu(String templateName, DOC loadExistingDoc, ViewContext<T> ctx,
List<MenuEntry> menuEntries);
protected abstract void writeDoc(DOC document, String viewPath);
@Override
public void addListView(String moduleName, JpaEntityMetadata entityMetadata,
MemberDetails entity, List<T> detailsControllers, ViewContext<T> ctx) {
// Get the repository related with the entity to check the default return type
RepositoryJpaMetadata repository =
getRepositoryJpaLocator().getFirstRepositoryMetadata(entityMetadata.getAnnotatedEntity());
// All views should have a repository
Validate.notNull(repository,
"ERROR: The provided entity should have an associated repository to be able "
+ "to generate the list view.");
// Obtain the defaultReturnType
JavaType defaultReturnType = repository.getDefaultReturnType();
// The defaultReturnType must not be null. If it's not an entity projection,
// it must be an entity
Validate
.notNull(defaultReturnType,
"ERROR: The repository associated to the provided entity should define a defaultReturnType");
// Obtain details of the provided defaultReturnType. If not exists as type, show an error
ClassOrInterfaceTypeDetails defaultReturnTypeCid =
getTypeLocationService().getTypeDetails(defaultReturnType);
Validate.notNull(defaultReturnTypeCid,
"ERROR: The provided defaultReturnType is not a valid type");
MemberDetails defaultReturnTypeDetails =
getMemberDetailsScanner().getMemberDetails(getClass().toString(), defaultReturnTypeCid);
Validate.notNull(defaultReturnTypeDetails,
"ERROR: Is not possible to obtain any detail from the " + "provided defaultReturnType.");
List<FieldMetadata> defaultReturnTypeFields = defaultReturnTypeDetails.getFields();
// To prevent errors during generation, is defaultReturnTypeFields is empty,
// all the entity fields will be used
if (defaultReturnTypeFields.isEmpty()) {
defaultReturnTypeFields = entity.getFields();
}
// Getting entity fields that should be included on view
List<FieldMetadata> entityFields = getPersistentFields(defaultReturnTypeFields);
List<FieldItem> fields =
getFieldViewItems(entityMetadata, entityFields, ctx.getEntityName(), true, ctx,
TABLE_SUFFIX);
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName =
getViewsFolder(moduleName).concat(ctx.getControllerPath()).concat("/").concat("/list")
.concat(getViewsExtension());
EntityItem entityItem = createEntityItem(entityMetadata, ctx, TABLE_SUFFIX);
List<List<DetailEntityItem>> detailsLevels = new ArrayList<List<DetailEntityItem>>();
if (detailsControllers != null && !detailsControllers.isEmpty()) {
List<DetailEntityItem> details = new ArrayList<DetailEntityItem>();
for (T detailController : detailsControllers) {
DetailEntityItem detailItem =
createDetailEntityItem(detailController, entity, entityMetadata, ctx.getEntityName(),
ctx, DETAIL_SUFFIX, entityItem);
details.add(detailItem);
}
// Sort details by path
Collections.sort(details, new Comparator<DetailEntityItem>() {
@Override
public int compare(DetailEntityItem o1, DetailEntityItem o2) {
return o1.getPathString().compareTo(o2.getPathString());
}
});
// Locates parent details for children, grandsons, etc and make groups by
// levels
for (DetailEntityItem detail : details) {
// Create group until item level
while (detailsLevels.size() < detail.getLevel()) {
detailsLevels.add(new ArrayList<DetailEntityItem>());
}
// Include detail in its group
detailsLevels.get(detail.getLevel() - 1).add(detail);
if (detail.getLevel() < 1) {
// Nothing more to do with detail
continue;
}
// look for parent
for (DetailEntityItem parent : details) {
if (detail.isTheParentEntity(parent)) {
// set parent
detail.setParentEntity(parent);
break;
}
}
}
}
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc =
mergeListView("list", loadExistingDoc(viewName), ctx, entityItem, fields, detailsLevels);
} else {
ctx.addExtraParameter("entity", entityItem);
ctx.addExtraParameter("fields", fields);
ctx.addExtraParameter("detailsLevels", detailsLevels);
newDoc = process("list", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
@Override
public void addListDeleteModalView(String moduleName, JpaEntityMetadata entityMetadata,
MemberDetails entity, ViewContext<T> ctx) {
// Get the repository related with the entity to check the default return type
RepositoryJpaMetadata repository =
getRepositoryJpaLocator().getFirstRepositoryMetadata(entityMetadata.getAnnotatedEntity());
// All views should have a repository
Validate.notNull(repository,
"ERROR: The provided entity should have an associated repository to be able "
+ "to generate the list view.");
// Obtain the defaultReturnType
JavaType defaultReturnType = repository.getDefaultReturnType();
// The defaultReturnType must not be null. If it's not an entity projection,
// it must be an entity
Validate
.notNull(defaultReturnType,
"ERROR: The repository associated to the provided entity should define a defaultReturnType");
// Obtain details of the provided defaultReturnType. If not exists as type, show an error
ClassOrInterfaceTypeDetails defaultReturnTypeCid =
getTypeLocationService().getTypeDetails(defaultReturnType);
Validate.notNull(defaultReturnTypeCid,
"ERROR: The provided defaultReturnType is not a valid type");
MemberDetails defaultReturnTypeDetails =
getMemberDetailsScanner().getMemberDetails(getClass().toString(), defaultReturnTypeCid);
Validate.notNull(defaultReturnTypeDetails,
"ERROR: Is not possible to obtain any detail from the " + "provided defaultReturnType.");
List<FieldMetadata> defaultReturnTypeFields = defaultReturnTypeDetails.getFields();
// To prevent errors during generation, is defaultReturnTypeFields is empty,
// all the entity fields will be used
if (defaultReturnTypeFields.isEmpty()) {
defaultReturnTypeFields = entity.getFields();
}
// Getting entity fields that should be included on view
List<FieldMetadata> entityFields = getPersistentFields(defaultReturnTypeFields);
List<FieldItem> fields =
getFieldViewItems(entityMetadata, entityFields, ctx.getEntityName(), true, ctx,
TABLE_SUFFIX);
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName =
getViewsFolder(moduleName).concat(ctx.getControllerPath()).concat("/")
.concat("/listDeleteModal").concat(getViewsExtension());
EntityItem entityItem = createEntityItem(entityMetadata, ctx, TABLE_SUFFIX);
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc =
mergeListDeleteModalView("listDeleteModal", loadExistingDoc(viewName), ctx, entityItem,
fields);
} else {
ctx.addExtraParameter("entity", entityItem);
ctx.addExtraParameter("fields", fields);
newDoc = process("listDeleteModal", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
@Override
public void addListDeleteModalDetailView(String moduleName, JpaEntityMetadata entityMetadata,
MemberDetails entity, ControllerMetadata controllerMetadata, ViewContext<T> ctx) {
// Get the repository related with the entity to check the default return type
RepositoryJpaMetadata repository =
getRepositoryJpaLocator().getFirstRepositoryMetadata(entityMetadata.getAnnotatedEntity());
// All views should have a repository
Validate.notNull(repository,
"ERROR: The provided entity should have an associated repository to be able "
+ "to generate the list view.");
// Obtain the defaultReturnType
JavaType defaultReturnType = repository.getDefaultReturnType();
// The defaultReturnType must not be null. If it's not an entity projection,
// it must be an entity
Validate
.notNull(defaultReturnType,
"ERROR: The repository associated to the provided entity should define a defaultReturnType");
// Obtain details of the provided defaultReturnType. If not exists as type, show an error
ClassOrInterfaceTypeDetails defaultReturnTypeCid =
getTypeLocationService().getTypeDetails(defaultReturnType);
Validate.notNull(defaultReturnTypeCid,
"ERROR: The provided defaultReturnType is not a valid type");
MemberDetails defaultReturnTypeDetails =
getMemberDetailsScanner().getMemberDetails(getClass().toString(), defaultReturnTypeCid);
Validate.notNull(defaultReturnTypeDetails,
"ERROR: Is not possible to obtain any detail from the " + "provided defaultReturnType.");
List<FieldMetadata> defaultReturnTypeFields = defaultReturnTypeDetails.getFields();
// To prevent errors during generation, is defaultReturnTypeFields is empty,
// all the entity fields will be used
if (defaultReturnTypeFields.isEmpty()) {
defaultReturnTypeFields = entity.getFields();
}
// Getting entity fields that should be included on view
List<FieldMetadata> entityFields = getPersistentFields(defaultReturnTypeFields);
List<FieldItem> fields =
getFieldViewItems(entityMetadata, entityFields, ctx.getEntityName(), true, ctx,
TABLE_SUFFIX);
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName =
getViewsFolder(moduleName).concat(ctx.getControllerPath()).concat("/")
.concat(controllerMetadata.getDetailsPathAsString("/")).concat("/listDeleteModal")
.concat(getViewsExtension());
EntityItem entityItem = createEntityItem(entityMetadata, ctx, TABLE_SUFFIX);
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc =
mergeListDeleteModalDetailView("listDeleteModalDetail", loadExistingDoc(viewName), ctx,
entityItem, fields);
} else {
ctx.addExtraParameter("entity", entityItem);
ctx.addExtraParameter("fields", fields);
newDoc = process("listDeleteModalDetail", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
@Override
public void addListDeleteModalBatchView(String moduleName, JpaEntityMetadata entityMetadata,
MemberDetails entity, ViewContext<T> ctx) {
// Get the repository related with the entity to check the default return type
RepositoryJpaMetadata repository =
getRepositoryJpaLocator().getFirstRepositoryMetadata(entityMetadata.getAnnotatedEntity());
// All views should have a repository
Validate.notNull(repository,
"ERROR: The provided entity should have an associated repository to be able "
+ "to generate the list view.");
// Obtain the defaultReturnType
JavaType defaultReturnType = repository.getDefaultReturnType();
// The defaultReturnType must not be null. If it's not an entity projection,
// it must be an entity
Validate
.notNull(defaultReturnType,
"ERROR: The repository associated to the provided entity should define a defaultReturnType");
// Obtain details of the provided defaultReturnType. If not exists as type, show an error
ClassOrInterfaceTypeDetails defaultReturnTypeCid =
getTypeLocationService().getTypeDetails(defaultReturnType);
Validate.notNull(defaultReturnTypeCid,
"ERROR: The provided defaultReturnType is not a valid type");
MemberDetails defaultReturnTypeDetails =
getMemberDetailsScanner().getMemberDetails(getClass().toString(), defaultReturnTypeCid);
Validate.notNull(defaultReturnTypeDetails,
"ERROR: Is not possible to obtain any detail from the " + "provided defaultReturnType.");
List<FieldMetadata> defaultReturnTypeFields = defaultReturnTypeDetails.getFields();
// To prevent errors during generation, is defaultReturnTypeFields is empty,
// all the entity fields will be used
if (defaultReturnTypeFields.isEmpty()) {
defaultReturnTypeFields = entity.getFields();
}
// Getting entity fields that should be included on view
List<FieldMetadata> entityFields = getPersistentFields(defaultReturnTypeFields);
List<FieldItem> fields =
getFieldViewItems(entityMetadata, entityFields, ctx.getEntityName(), true, ctx,
TABLE_SUFFIX);
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName =
getViewsFolder(moduleName).concat(ctx.getControllerPath()).concat("/")
.concat("/listDeleteModalBatch").concat(getViewsExtension());
EntityItem entityItem = createEntityItem(entityMetadata, ctx, TABLE_SUFFIX);
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc =
mergeListDeleteModalBatchView("listDeleteModalBatch", loadExistingDoc(viewName), ctx,
entityItem, fields);
} else {
ctx.addExtraParameter("entity", entityItem);
ctx.addExtraParameter("fields", fields);
newDoc = process("listDeleteModalBatch", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
@Override
public void addListDeleteModalDetailBatchView(String moduleName,
JpaEntityMetadata entityMetadata, MemberDetails entity,
ControllerMetadata controllerMetadata, ViewContext<T> ctx) {
// Get the repository related with the entity to check the default return type
RepositoryJpaMetadata repository =
getRepositoryJpaLocator().getFirstRepositoryMetadata(entityMetadata.getAnnotatedEntity());
// All views should have a repository
Validate.notNull(repository,
"ERROR: The provided entity should have an associated repository to be able "
+ "to generate the list view.");
// Obtain the defaultReturnType
JavaType defaultReturnType = repository.getDefaultReturnType();
// The defaultReturnType must not be null. If it's not an entity projection,
// it must be an entity
Validate
.notNull(defaultReturnType,
"ERROR: The repository associated to the provided entity should define a defaultReturnType");
// Obtain details of the provided defaultReturnType. If not exists as type, show an error
ClassOrInterfaceTypeDetails defaultReturnTypeCid =
getTypeLocationService().getTypeDetails(defaultReturnType);
Validate.notNull(defaultReturnTypeCid,
"ERROR: The provided defaultReturnType is not a valid type");
MemberDetails defaultReturnTypeDetails =
getMemberDetailsScanner().getMemberDetails(getClass().toString(), defaultReturnTypeCid);
Validate.notNull(defaultReturnTypeDetails,
"ERROR: Is not possible to obtain any detail from the " + "provided defaultReturnType.");
List<FieldMetadata> defaultReturnTypeFields = defaultReturnTypeDetails.getFields();
// To prevent errors during generation, is defaultReturnTypeFields is empty,
// all the entity fields will be used
if (defaultReturnTypeFields.isEmpty()) {
defaultReturnTypeFields = entity.getFields();
}
// Getting entity fields that should be included on view
List<FieldMetadata> entityFields = getPersistentFields(defaultReturnTypeFields);
List<FieldItem> fields =
getFieldViewItems(entityMetadata, entityFields, ctx.getEntityName(), true, ctx,
TABLE_SUFFIX);
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName =
getViewsFolder(moduleName).concat(ctx.getControllerPath()).concat("/")
.concat(controllerMetadata.getDetailsPathAsString("/")).concat("/listDeleteModalBatch")
.concat(getViewsExtension());
EntityItem entityItem = createEntityItem(entityMetadata, ctx, TABLE_SUFFIX);
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc =
mergeListDeleteModalBatchDetailView("listDeleteModalBatchDetail",
loadExistingDoc(viewName), ctx, entityItem, fields);
} else {
ctx.addExtraParameter("entity", entityItem);
ctx.addExtraParameter("fields", fields);
newDoc = process("listDeleteModalBatchDetail", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
protected List<FieldMetadata> getPersistentFields(List<FieldMetadata> fields) {
List<FieldMetadata> result = new ArrayList<FieldMetadata>(fields.size());
for (FieldMetadata field : fields) {
int modifier = field.getModifier();
if (Modifier.isStatic(modifier) || Modifier.isFinal(modifier)
|| Modifier.isTransient(modifier)) {
continue;
}
if (field.getAnnotation(JpaJavaType.TRANSIENT) != null) {
continue;
}
result.add(field);
}
return result;
}
protected List<FieldMetadata> getEditableFields(List<FieldMetadata> fields) {
List<FieldMetadata> persistentFields = getPersistentFields(fields);
Iterator<FieldMetadata> it = persistentFields.iterator();
while (it.hasNext()) {
FieldMetadata field = it.next();
if (field.getAnnotation(SpringJavaType.LAST_MODIFIED_DATE) != null
|| field.getAnnotation(SpringJavaType.LAST_MODIFIED_BY) != null
|| field.getAnnotation(SpringJavaType.CREATED_BY) != null
|| field.getAnnotation(SpringJavaType.CREATED_DATE) != null) {
it.remove();
}
}
return persistentFields;
}
protected EntityItem createEntityItem(JpaEntityMetadata entityMetadata, ViewContext<T> ctx,
String suffix) {
return new EntityItem(ctx.getEntityName(), ctx.getIdentifierField(), ctx.getControllerPath(),
suffix, entityMetadata.isReadOnly(), entityMetadata.getCurrentVersionField().getFieldName()
.getSymbolName());
}
@Override
public void addShowView(String moduleName, JpaEntityMetadata entityMetadata,
MemberDetails entityDetails, List<T> detailsControllers, ViewContext<T> ctx) {
// Getting entity fields that should be included on view
List<FieldMetadata> entityFields = new ArrayList<FieldMetadata>();
EntityItem entityItem = createEntityItem(entityMetadata, ctx, TABLE_SUFFIX);
Map<String, List<FieldItem>> compositeRelationFields =
manageChildcompositionFields(entityMetadata, entityDetails, ctx);
// Remove one-to-one fields from composite relations and create EntityItems
// for each referenced entity field
Set<String> compositeRelationFieldNames = compositeRelationFields.keySet();
for (FieldMetadata field : getPersistentFields(entityDetails.getFields())) {
if (!compositeRelationFieldNames.contains(field.getFieldName().getSymbolName())) {
entityFields.add(field);
}
}
List<FieldItem> fields =
getFieldViewItems(entityMetadata, entityFields, ctx.getEntityName(), false, ctx,
FIELD_SUFFIX);
ctx.addExtraParameter("fields", fields);
ctx.addExtraParameter("entity", entityItem);
ctx.addExtraParameter("compositeRelationFields", compositeRelationFields);
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName =
getViewsFolder(moduleName).concat(ctx.getControllerPath()).concat("/").concat("/show")
.concat(getViewsExtension());
// Including details if needed
List<List<DetailEntityItem>> detailsLevels = new ArrayList<List<DetailEntityItem>>();
if (detailsControllers != null && !detailsControllers.isEmpty()) {
List<DetailEntityItem> details = new ArrayList<DetailEntityItem>();
for (T detailController : detailsControllers) {
DetailEntityItem detailItem =
createDetailEntityItem(detailController, entityDetails, entityMetadata,
ctx.getEntityName(), ctx, DETAIL_SUFFIX, entityItem);
details.add(detailItem);
}
// Sort details by path
Collections.sort(details, new Comparator<DetailEntityItem>() {
@Override
public int compare(DetailEntityItem o1, DetailEntityItem o2) {
return o1.getPathString().compareTo(o2.getPathString());
}
});
// Locates parent details for children, grandsons, etc and make groups by
// levels
for (DetailEntityItem detail : details) {
// Create group until item level
while (detailsLevels.size() < detail.getLevel()) {
detailsLevels.add(new ArrayList<DetailEntityItem>());
}
// Include detail in its group
detailsLevels.get(detail.getLevel() - 1).add(detail);
if (detail.getLevel() < 1) {
// Nothing more to do with detail
continue;
}
// look for parent
for (DetailEntityItem parent : details) {
if (detail.isTheParentEntity(parent)) {
// set parent
detail.setParentEntity(parent);
break;
}
}
}
}
ctx.addExtraParameter("detailsLevels", detailsLevels);
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc = merge("show", loadExistingDoc(viewName), ctx, fields);
} else {
newDoc = process("show", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
@Override
public void addShowInlineView(String moduleName, JpaEntityMetadata entityMetadata,
MemberDetails entityDetails, ViewContext<T> ctx) {
// Getting entity fields that should be included on view
List<FieldMetadata> entityFields = new ArrayList<FieldMetadata>();
EntityItem entityItem = createEntityItem(entityMetadata, ctx, TABLE_SUFFIX);
Map<String, List<FieldItem>> compositeRelationFields =
manageChildcompositionFields(entityMetadata, entityDetails, ctx);
// Remove one-to-one fields from composite relations and create EntityItems
// for each referenced entity field
Set<String> compositeRelationFieldNames = compositeRelationFields.keySet();
for (FieldMetadata field : getPersistentFields(entityDetails.getFields())) {
if (!compositeRelationFieldNames.contains(field.getFieldName().getSymbolName())) {
entityFields.add(field);
}
}
List<FieldItem> fields =
getFieldViewItems(entityMetadata, entityFields, ctx.getEntityName(), false, ctx,
FIELD_SUFFIX);
ctx.addExtraParameter("fields", fields);
ctx.addExtraParameter("entity", entityItem);
ctx.addExtraParameter("compositeRelationFields", compositeRelationFields);
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName =
getViewsFolder(moduleName).concat(ctx.getControllerPath()).concat("/")
.concat("/showInline").concat(getViewsExtension());
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc = merge("showInline", loadExistingDoc(viewName), ctx, fields);
} else {
newDoc = process("showInline", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
@Override
public void addCreateView(String moduleName, JpaEntityMetadata entityMetadata,
MemberDetails entityDetails, ViewContext<T> ctx) {
// Create void list for adding main entity fields
List<FieldMetadata> entityFields = new ArrayList<FieldMetadata>();
EntityItem entityItem = createEntityItem(entityMetadata, ctx, TABLE_SUFFIX);
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName =
getViewsFolder(moduleName).concat(ctx.getControllerPath()).concat("/").concat("/create")
.concat(getViewsExtension());
Map<String, List<FieldItem>> compositeRelationFields =
manageChildcompositionFields(entityMetadata, entityDetails, ctx);
// Remove one-to-one fields from composite relations and create EntityItems
// for each referenced entity field
Set<String> compositeRelationFieldNames = compositeRelationFields.keySet();
for (FieldMetadata field : getEditableFields(entityDetails.getFields())) {
if (!compositeRelationFieldNames.contains(field.getFieldName().getSymbolName())) {
entityFields.add(field);
}
}
List<FieldItem> fields =
getFieldViewItems(entityMetadata, entityFields, ctx.getEntityName(), false, ctx,
FIELD_SUFFIX);
ctx.addExtraParameter("fields", fields);
ctx.addExtraParameter("entity", entityItem);
ctx.addExtraParameter("compositeRelationFields", compositeRelationFields);
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc = merge("create", loadExistingDoc(viewName), ctx, fields);
} else {
newDoc = process("create", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
@Override
public void addUpdateView(String moduleName, JpaEntityMetadata entityMetadata,
MemberDetails entityDetails, ViewContext<T> ctx) {
// Getting entity fields that should be included on view
List<FieldMetadata> entityFields = new ArrayList<FieldMetadata>();
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName =
getViewsFolder(moduleName).concat(ctx.getControllerPath()).concat("/").concat("/edit")
.concat(getViewsExtension());
// Check and manage detail composition fields
Map<String, List<FieldItem>> compositeRelationFields =
manageChildcompositionFields(entityMetadata, entityDetails, ctx);
// Remove one-to-one fields from composite relations and create EntityItems
// for each referenced entity field
Set<String> compositeRelationFieldNames = compositeRelationFields.keySet();
for (FieldMetadata field : getEditableFields(entityDetails.getFields())) {
if (!compositeRelationFieldNames.contains(field.getFieldName().getSymbolName())) {
entityFields.add(field);
}
}
List<FieldItem> fields =
getFieldViewItems(entityMetadata, entityFields, ctx.getEntityName(), false, ctx,
FIELD_SUFFIX);
EntityItem entityItem = createEntityItem(entityMetadata, ctx, TABLE_SUFFIX);
ctx.addExtraParameter("fields", fields);
ctx.addExtraParameter("entity", entityItem);
ctx.addExtraParameter("compositeRelationFields", compositeRelationFields);
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc = merge("edit", loadExistingDoc(viewName), ctx, fields);
} else {
newDoc = process("edit", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
@Override
public void addFinderFormView(String moduleName, JpaEntityMetadata entityMetadata,
T viewMetadata, JavaType formBean, String finderName, ViewContext<T> ctx) {
// To be implemented by View Provider
}
@Override
public void addFinderListView(String moduleName, JpaEntityMetadata entityMetadata,
MemberDetails entity, T viewMetadata, JavaType formBean, JavaType returnType,
String finderName, List<T> detailsControllers, ViewContext<T> ctx) {
// To be implemented by View Provider
}
@Override
public void addIndexView(String moduleName, ViewContext<T> ctx) {
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName = getViewsFolder(moduleName).concat("/index").concat(getViewsExtension());
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc = merge("index", loadExistingDoc(viewName), ctx);
} else {
newDoc = process("index", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
@Override
public void addLoginView(String moduleName, ViewContext<T> ctx) {
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName = getViewsFolder(moduleName).concat("/login").concat(getViewsExtension());
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc = merge("login", loadExistingDoc(viewName), ctx);
} else {
newDoc = process("login", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
@Override
public void addAccessibilityView(String moduleName, ViewContext<T> ctx) {
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName =
getViewsFolder(moduleName).concat("/accessibility").concat(getViewsExtension());
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc = merge("accessibility", loadExistingDoc(viewName), ctx);
} else {
newDoc = process("accessibility", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
@Override
public void addErrorView(String moduleName, ViewContext<T> ctx) {
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName = getViewsFolder(moduleName).concat("/error").concat(getViewsExtension());
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc = merge("error", loadExistingDoc(viewName), ctx);
} else {
newDoc = process("error", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
@Override
public void addDefaultLayout(String moduleName, ViewContext<T> ctx) {
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName =
getLayoutsFolder(moduleName).concat("/default-layout").concat(getViewsExtension());
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc = merge("layouts/default-layout", loadExistingDoc(viewName), ctx);
} else {
newDoc = process("layouts/default-layout", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
@Override
public void addDefaultLayoutNoMenu(String moduleName, ViewContext<T> ctx) {
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName =
getLayoutsFolder(moduleName).concat("/default-layout-no-menu").concat(getViewsExtension());
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc = merge("layouts/default-layout-no-menu", loadExistingDoc(viewName), ctx);
} else {
newDoc = process("layouts/default-layout-no-menu", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
@Override
public void addHomeLayout(String moduleName, ViewContext<T> ctx) {
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName =
getLayoutsFolder(moduleName).concat("/home-layout").concat(getViewsExtension());
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc = merge("layouts/home-layout", loadExistingDoc(viewName), ctx);
} else {
newDoc = process("layouts/home-layout", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
@Override
public void addFooter(String moduleName, ViewContext<T> ctx) {
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName = getFragmentsFolder(moduleName).concat("/footer").concat(getViewsExtension());
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc = merge("fragments/footer", loadExistingDoc(viewName), ctx);
} else {
newDoc = process("fragments/footer", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
@Override
public void addHeader(String moduleName, ViewContext<T> ctx) {
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName = getFragmentsFolder(moduleName).concat("/header").concat(getViewsExtension());
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc = merge("fragments/header", loadExistingDoc(viewName), ctx);
} else {
newDoc = process("fragments/header", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
@Override
public void addMenu(String moduleName, ViewContext<T> ctx) {
Map<String, MenuEntry> mapMenuEntries = new TreeMap<String, MenuEntry>();
Set<ClassOrInterfaceTypeDetails> existingControllers =
new HashSet<ClassOrInterfaceTypeDetails>();
existingControllers.addAll(getControllerLocator().getControllers(null,
ControllerType.COLLECTION, getType()));
existingControllers.addAll(getControllerLocator().getControllers(null, ControllerType.SEARCH,
getType()));
Iterator<ClassOrInterfaceTypeDetails> it = existingControllers.iterator();
while (it.hasNext()) {
// Getting controller and its information
ClassOrInterfaceTypeDetails controller = it.next();
ControllerAnnotationValues controllerValues = new ControllerAnnotationValues(controller);
JavaType entity = controllerValues.getEntity();
// Obtain the entityMetadata
JpaEntityMetadata entityMetadata =
getMetadataService().get(
JpaEntityMetadata.createIdentifier(getTypeLocationService().getTypeDetails(entity)));
boolean isReadOnly = false;
if (entityMetadata != null && entityMetadata.isReadOnly()) {
isReadOnly = true;
}
// Get finders for each controller
AnnotationMetadata controllerSearchAnnotation =
controller.getAnnotation(RooJavaType.ROO_SEARCH);
Map<String, String> finderNamesAndPaths = new HashMap<String, String>();
if (controllerSearchAnnotation != null
&& controllerSearchAnnotation.getAttribute("finders") != null) {
List<?> finders = (List<?>) controllerSearchAnnotation.getAttribute("finders").getValue();
Iterator<?> iterator = finders.iterator();
while (iterator.hasNext()) {
StringAttributeValue attributeValue = (StringAttributeValue) iterator.next();
String finderName = attributeValue.getValue();
// Build URL path to get data
String finderPath = "";
if (StringUtils.startsWith(finderName, "count")) {
finderPath = StringUtils.removeStart(finderName, "count");
} else if (StringUtils.startsWith(finderName, "find")) {
finderPath = StringUtils.removeStart(finderName, "find");
} else if (StringUtils.startsWith(finderName, "query")) {
finderPath = StringUtils.removeStart(finderName, "query");
} else if (StringUtils.startsWith(finderName, "read")) {
finderPath = StringUtils.removeStart(finderName, "read");
} else {
finderPath = finderName;
}
finderPath = String.format("search/%s/search-form", StringUtils.uncapitalize(finderPath));
finderNamesAndPaths.put(finderName, finderPath);
}
}
// Getting pathPrefix
String pathPrefix = StringUtils.defaultString(controllerValues.getPathPrefix(), "");
// Generate path
String path = getControllerOperations().getBaseUrlForController(controller);
if (controllerSearchAnnotation != null) {
// Prevent that /search will be included in every menu entry
// The /search is already included in the step before only for
// finder entries
path = path.replace("/search", "");
}
// Create new menuEntry element for controller
String keyThatRepresentsEntry = pathPrefix.concat(entity.getSimpleTypeName());
// Add new menu entry to menuEntries list if doesn't exist
MenuEntry menuEntry = null;
if (controllerValues.getType() == ControllerType.SEARCH) {
// Only add finder entry
menuEntry =
createMenuEntry(entity.getSimpleTypeName(), path, pathPrefix,
FieldItem.buildLabel(entity.getSimpleTypeName(), ""),
FieldItem.buildLabel(entity.getSimpleTypeName(), "plural"), finderNamesAndPaths,
false, false, false);
} else {
// Add default menu entries
menuEntry =
createMenuEntry(entity.getSimpleTypeName(), path, pathPrefix,
FieldItem.buildLabel(entity.getSimpleTypeName(), ""),
FieldItem.buildLabel(entity.getSimpleTypeName(), "plural"), finderNamesAndPaths,
false, true, isReadOnly);
}
if (mapMenuEntries.containsKey(keyThatRepresentsEntry)) {
MenuEntry menuEntryInserted = mapMenuEntries.get(keyThatRepresentsEntry);
if (menuEntryInserted.getFinderNamesAndPaths().isEmpty()
&& !menuEntry.getFinderNamesAndPaths().isEmpty()) {
menuEntryInserted.setFinderNamesAndPaths(menuEntry.getFinderNamesAndPaths());
}
// Check the 'addDefaultEntries' attribute and add it if needed
if (!menuEntryInserted.isAddDefaultEntries() && menuEntry.isAddDefaultEntries()) {
menuEntryInserted.setAddDefaultEntries(menuEntry.isAddDefaultEntries());
}
} else {
mapMenuEntries.put(keyThatRepresentsEntry, menuEntry);
}
}
// Also, check web flow views in the views folder
String viewsFolder = getViewsFolder(moduleName);
List<String> webFlowViews = getWebFlowViewsFromDir(viewsFolder, null);
// After obtain the webFlow views, add them to the menu
for (String webFlowView : webFlowViews) {
// Creating the menu entry
MenuEntry menuEntry =
createMenuEntry(webFlowView, webFlowView, "", FieldItem.buildLabel(webFlowView, ""),
FieldItem.buildLabel(webFlowView, "plural"), null, true, false, false);
mapMenuEntries.put(webFlowView, menuEntry);
}
// First of all, generate a list of MenuEntries based on existing
// controllers
List<MenuEntry> menuEntries = new ArrayList<MenuEntry>(mapMenuEntries.values());
// Generate ids to search when merge new and existing doc
List<String> requiredIds = new ArrayList<String>();
for (MenuEntry entry : menuEntries) {
requiredIds.add(entry.getPathPrefix().concat(entry.getEntityName()).concat("Entry"));
}
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName = getFragmentsFolder(moduleName).concat("/menu").concat(getViewsExtension());
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc = mergeMenu("fragments/menu", loadExistingDoc(viewName), ctx, menuEntries);
} else {
ctx.addExtraParameter("menuEntries", menuEntries);
newDoc = process("fragments/menu", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
protected MenuEntry createMenuEntry(String entityName, String path, String pathPrefix,
String entityLabel, String entityPluralLabel, Map<String, String> finderNamesAndPaths,
boolean simple, boolean addDefaultEntries, boolean readOnly) {
return new MenuEntry(entityName, path, pathPrefix, entityLabel, entityPluralLabel,
finderNamesAndPaths, simple, addDefaultEntries, readOnly);
}
/**
* THis method obtains the WebFlow views generated in the project.
*
* @param viewsFolder
* @param views
* @return
*/
private List<String> getWebFlowViewsFromDir(String viewsFolder, List<String> views) {
if (views == null) {
views = new ArrayList<String>();
}
File viewsDir = new File(viewsFolder);
File[] allElements = viewsDir.listFiles();
for (File element : allElements) {
if (element.isDirectory()) {
getWebFlowViewsFromDir(element.getAbsolutePath(), views);
} else if (element.getName().endsWith("-flow.xml")) {
String flowName = element.getName().replaceAll("-flow.xml", "");
views.add(flowName);
}
}
return views;
}
@Override
public void addModal(String moduleName, ViewContext<T> ctx) {
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName = getFragmentsFolder(moduleName).concat("/modal").concat(getViewsExtension());
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc = merge("fragments/modal", loadExistingDoc(viewName), ctx);
} else {
newDoc = process("fragments/modal", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
@Override
public void addModalConfirm(String moduleName, ViewContext<T> ctx) {
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName =
getFragmentsFolder(moduleName).concat("/modal-confirm").concat(getViewsExtension());
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc = merge("fragments/modal-confirm", loadExistingDoc(viewName), ctx);
} else {
newDoc = process("fragments/modal-confirm", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
@Override
public void addSessionLinks(String moduleName, ViewContext<T> ctx) {
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName =
getFragmentsFolder(moduleName).concat("/session-links").concat(getViewsExtension());
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc = merge("fragments/session-links", loadExistingDoc(viewName), ctx);
} else {
newDoc = process("fragments/session-links", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
@Override
public void addLanguages(String moduleName, ViewContext<T> ctx) {
// Add installed languages
List<I18n> installedLanguages = getI18nOperations().getInstalledLanguages(moduleName);
ctx.addExtraParameter("languages", installedLanguages);
// Process elements to generate
DOC newDoc = null;
// Getting new viewName
String viewName =
getFragmentsFolder(moduleName).concat("/languages").concat(getViewsExtension());
// Generate ids to search when merge new and existing doc
List<String> requiredIds = new ArrayList<String>();
for (I18n language : installedLanguages) {
requiredIds.add(language.getLocale().getLanguage() + "Flag");
}
// Check if new view to generate exists or not
if (existsFile(viewName)) {
newDoc = merge("fragments/languages", loadExistingDoc(viewName), ctx);
} else {
newDoc = process("fragments/languages", ctx);
}
// Write newDoc on disk
writeDoc(newDoc, viewName);
}
@Override
public void updateMenuView(String moduleName, ViewContext<T> ctx) {
// TODO: This method should update menu view with the new
// controller to include, instead of regenerate menu view page.
addMenu(moduleName, ctx);
addLanguages(moduleName, ctx);
}
@Override
public String getLayoutsFolder(String moduleName) {
// Default implementation
return getViewsFolder(moduleName);
}
@Override
public String getFragmentsFolder(String moduleName) {
// Default implementation
return getViewsFolder(moduleName);
}
/**
* This method obtains all necessary information about fields from entity and
* returns a List of FieldItem. If provided entity has more than 5 fields,
* only the first 5 ones will be included on generated view.
*
* @param entityMetadata
* @param fields
* @param entityName
* @param checkMaxFields
* @param ctx
* @param suffixId
* @return List that contains FieldMetadata that will be added to the view.
*/
protected List<FieldItem> getFieldViewItems(JpaEntityMetadata entityMetadata,
List<FieldMetadata> entityFields, String entityName, boolean checkMaxFields,
ViewContext<T> ctx, String suffixId) {
// Get the MAX_FIELDS_TO_ADD
List<FieldItem> fieldViewItems = new ArrayList<FieldItem>();
for (FieldMetadata entityField : entityFields) {
FieldItem fieldItem =
createFieldItem(entityMetadata, entityField, entityName, suffixId, ctx, null);
if (fieldItem != null) {
fieldViewItems.add(fieldItem);
}
if (fieldViewItems.size() >= MAX_FIELDS_TO_ADD && checkMaxFields) {
break;
}
}
return fieldViewItems;
}
protected FieldItem createFieldItem(JpaEntityMetadata entityMetadata, FieldMetadata entityField,
String entityName, String suffixId, ViewContext<T> ctx, String referencedFieldName) {
// Getting current identifier field
FieldMetadata identifierField = entityMetadata.getCurrentIndentifierField();
FieldMetadata versionField = entityMetadata.getCurrentVersionField();
// Exclude id and version fields
if (entityField.getAnnotation(JpaJavaType.ID) != null
|| entityField.getAnnotation(JpaJavaType.VERSION) != null
|| identifierField.getFieldName().equals(entityField.getFieldName())
|| versionField.getFieldName().equals(entityField.getFieldName())) {
return null;
}
// Generating new FieldItem element
FieldItem fieldItem = null;
if (referencedFieldName != null) {
fieldItem =
new FieldItem(entityField.getFieldName().getSymbolName(), ctx.getEntityName(),
referencedFieldName, entityName, suffixId);
} else {
fieldItem = new FieldItem(entityField.getFieldName().getSymbolName(), entityName, suffixId);
}
// Calculate fieldType
JavaType type = entityField.getFieldType();
ClassOrInterfaceTypeDetails typeDetails = getTypeLocationService().getTypeDetails(type);
// ROO-3810: Getting @NotNull annotation to include required attr
AnnotationMetadata notNullAnnotation = entityField.getAnnotation(Jsr303JavaType.NOT_NULL);
if (notNullAnnotation != null) {
fieldItem.addConfigurationElement("required", true);
} else {
fieldItem.addConfigurationElement("required", false);
}
// Check if is a referenced field
if (typeDetails != null && typeDetails.getAnnotation(RooJavaType.ROO_JPA_ENTITY) != null) {
// Referenced field is a relation field
// If is child part field of a composition relation, specify it if view context
if (entityMetadata.getCompositionRelationField() != null
&& entityMetadata.getCompositionRelationField().getFieldName()
.equals(entityField.getFieldName())) {
fieldItem.addConfigurationElement("isCompositionChildField", true);
} else {
fieldItem.addConfigurationElement("isCompositionChildField", false);
}
boolean shouldBeAdded = getReferenceField(fieldItem, typeDetails, ctx);
if (!shouldBeAdded) {
return null;
}
} else if (type.isBoolean()) {
// Check if is a boolean field
fieldItem.setType(FieldTypes.BOOLEAN.toString());
} else if (typeDetails != null
&& typeDetails.getPhysicalTypeCategory().equals(PhysicalTypeCategory.ENUMERATION)) {
// Saving enum and items to display. Same name as
// populateForm method
fieldItem.setType(FieldTypes.ENUM.toString());
fieldItem.addConfigurationElement("items", fieldItem.getFieldName());
} else if (type.getFullyQualifiedTypeName().equals(Date.class.getName())
|| type.getFullyQualifiedTypeName().equals(Calendar.class.getName())) {
// Check if is a date field
fieldItem.setType(FieldTypes.DATE.toString());
// Getting datetime format to use
AnnotationMetadata dateTimeFormatAnnotation =
entityField.getAnnotation(SpringJavaType.DATE_TIME_FORMAT);
String format = "d/m/Y";
if (dateTimeFormatAnnotation != null) {
AnnotationAttributeValue<String> styleAttribute =
dateTimeFormatAnnotation.getAttribute("style");
if (styleAttribute != null) {
String annotationFormat = styleAttribute.getValue();
if (annotationFormat.equals("M-")) {
format = "d-M-Y";
} else {
format = annotationFormat;
}
}
}
fieldItem.addConfigurationElement("format", format);
} else if (type.getFullyQualifiedTypeName().equals("java.util.Set")
|| type.getFullyQualifiedTypeName().equals("java.util.List")) {
// Ignore details. To obtain details uses
// getDetailsFieldViewItems method
return null;
} else if (type.isNumber()) {
// ROO-3810: Getting @Min and @Max annotations to add validations if
// necessary
AnnotationMetadata minAnnotation = entityField.getAnnotation(Jsr303JavaType.MIN);
if (minAnnotation != null) {
AnnotationAttributeValue<Object> min = minAnnotation.getAttribute("value");
if (min != null) {
fieldItem.addConfigurationElement("min", min.getValue().toString());
} else {
fieldItem.addConfigurationElement("min", "NULL");
}
} else {
fieldItem.addConfigurationElement("min", "NULL");
}
AnnotationMetadata maxAnnotation = entityField.getAnnotation(Jsr303JavaType.MAX);
if (maxAnnotation != null) {
AnnotationAttributeValue<Object> max = maxAnnotation.getAttribute("value");
if (max != null) {
fieldItem.addConfigurationElement("max", max.getValue().toString());
} else {
fieldItem.addConfigurationElement("max", "NULL");
}
} else {
fieldItem.addConfigurationElement("max", "NULL");
}
// ROO-3872: Support numeric input validation from client and server side
// Add digits constraints
AnnotationMetadata digitsAnnotation = entityField.getAnnotation(Jsr303JavaType.DIGITS);
if (digitsAnnotation != null) {
// Fraction digits
AnnotationAttributeValue<Object> digitsFraction = digitsAnnotation.getAttribute("fraction");
fieldItem.addConfigurationElement("digitsFraction", digitsFraction.getValue().toString());
// Integer digits
AnnotationAttributeValue<Object> digitsInteger = digitsAnnotation.getAttribute("integer");
fieldItem.addConfigurationElement("digitsInteger", digitsInteger.getValue().toString());
} else {
// Add default values for decimals
if (type.equals(JavaType.INT_OBJECT) || type.equals(JavaType.INT_PRIMITIVE)
|| type.equals(JdkJavaType.BIG_INTEGER)) {
fieldItem.addConfigurationElement("digitsFraction", "0");
} else {
fieldItem.addConfigurationElement("digitsFraction", "2");
}
fieldItem.addConfigurationElement("digitsInteger", "NULL");
}
fieldItem.setType(FieldTypes.NUMBER.toString());
} else {
// ROO-3810: Getting @Size annotation
AnnotationMetadata sizeAnnotation = entityField.getAnnotation(Jsr303JavaType.SIZE);
if (sizeAnnotation != null) {
AnnotationAttributeValue<Object> maxLength = sizeAnnotation.getAttribute("max");
if (maxLength != null) {
fieldItem.addConfigurationElement("maxLength", maxLength.getValue().toString());
} else {
fieldItem.addConfigurationElement("maxLength", "NULL");
}
} else {
fieldItem.addConfigurationElement("maxLength", "NULL");
}
fieldItem.setType(FieldTypes.TEXT.toString());
}
return fieldItem;
}
@Override
public void addDetailsViews(String moduleName, JpaEntityMetadata entityMetadata,
MemberDetails entity, ControllerMetadata controllerMetadata, T viewMetadata,
ViewContext<T> ctx) {
// Nothing to do here
}
@Override
public void addDetailsItemViews(String moduleName, JpaEntityMetadata entityMetadata,
MemberDetails entity, ControllerMetadata controllerMetadata, T viewMetadata,
ViewContext<T> ctx) {
// Nothing to do here
}
/**
* Create a new instance of {@link DetailEntityItem}. Implementation can
* override this method to include it own information or extend defaults.
*
* @param detailController
* @param detailSuffix
* @param ctx
* @param string
* @param entityMetadata
* @param entity
* @return
*/
protected DetailEntityItem createDetailEntityItem(T detailController,
MemberDetails entityMembers, JpaEntityMetadata entityMetadata, String entityName,
ViewContext<T> ctx, String detailSuffix, EntityItem rootEntity) {
ControllerMetadata controllerMetadata = detailController.getControllerMetadata();
RelationInfoExtended last = controllerMetadata.getLastDetailsInfo();
ClassOrInterfaceTypeDetails childEntityDetails =
getTypeLocationService().getTypeDetails(last.childType);
JpaEntityMetadata childEntityMetadata = last.childEntityMetadata;
String controllerPath =
getControllerOperations().getBasePathForController(controllerMetadata.getDestination());
DetailEntityItem detailItem =
new DetailEntityItem(childEntityMetadata, controllerMetadata, controllerPath, detailSuffix,
rootEntity);
// Saving necessary configuration
detailItem.addConfigurationElement("referencedFieldType", last.childType.getSimpleTypeName());
// Getting identifier field
detailItem.addConfigurationElement("identifierField", childEntityMetadata
.getCurrentIndentifierField().getFieldName().getSymbolName());
// Getting referencedfield label plural
detailItem.addConfigurationElement("referencedFieldLabel",
FieldItem.buildLabel(entityName, last.fieldName));
// Getting all referenced fields
List<FieldMetadata> referencedFields = null;
// Get the repository related with the child entity to check the default return type
RepositoryJpaMetadata repository =
getRepositoryJpaLocator().getFirstRepositoryMetadata(
childEntityMetadata.getAnnotatedEntity());
// All views should have a repository
Validate.notNull(repository,
"ERROR: The provided child entity should have an associated repository to be able "
+ "to generate the list view.");
// Obtain the defaultReturnType
JavaType defaultReturnType = repository.getDefaultReturnType();
// The defaultReturnType must not be null. If it's not an entity projection,
// it must be an entity
Validate
.notNull(defaultReturnType,
"ERROR: The repository associated to the provided entity should define a defaultReturnType");
// Obtain details of the provided defaultReturnType. If not exists as type, show an error
ClassOrInterfaceTypeDetails defaultReturnTypeCid =
getTypeLocationService().getTypeDetails(defaultReturnType);
Validate.notNull(defaultReturnTypeCid,
"ERROR: The provided defaultReturnType is not a valid type");
MemberDetails defaultReturnTypeDetails =
getMemberDetailsScanner().getMemberDetails(getClass().toString(), defaultReturnTypeCid);
Validate.notNull(defaultReturnTypeDetails,
"ERROR: Is not possible to obtain any detail from the " + "provided defaultReturnType.");
List<FieldMetadata> defaultReturnTypeFields = defaultReturnTypeDetails.getFields();
if (defaultReturnTypeFields.isEmpty()) {
referencedFields =
getEditableFields(getMemberDetailsScanner().getMemberDetails(getClass().toString(),
childEntityDetails).getFields());
} else {
referencedFields = getEditableFields(defaultReturnTypeFields);
}
detailItem.addConfigurationElement(
"referenceFieldFields",
getFieldViewItems(childEntityMetadata, referencedFields, entityName + "." + last.fieldName,
true, ctx, StringUtils.EMPTY));
detailItem.addConfigurationElement(
"fields",
getFieldViewItems(childEntityMetadata, referencedFields, detailItem.getEntityName(), true,
ctx, StringUtils.EMPTY));
return detailItem;
}
protected MetadataService getMetadataService() {
return serviceInstaceManager.getServiceInstance(this, MetadataService.class);
}
/**
* This method obtains all necessary configuration to be able to work with
* reference fields. Complete provided FieldItem with extra fields. If some
* extra configuration is not available, returns false to prevent that this
* field will be added. If everything is ok, returns true to add this field to
* generated view.
*
* @param fieldItem
* @param typeDetails
* @param allControllers
* @return
*/
protected boolean getReferenceField(FieldItem fieldItem, ClassOrInterfaceTypeDetails typeDetails,
ViewContext<T> viewContext) {
// Set type as REFERENCE
fieldItem.setType(FieldTypes.REFERENCE.toString());
// Add referencedEntity to configuration
fieldItem
.addConfigurationElement("referencedEntity", typeDetails.getType().getSimpleTypeName());
// Add the controllerPath related to the referencedEntity to
// configuration
final String controllerPrefix =
viewContext.getViewMetadata().getControllerMetadata().getAnnotationValues().getPathPrefix();
Collection<ClassOrInterfaceTypeDetails> allControllers =
getControllerLocator().getControllers(typeDetails.getType(), ControllerType.COLLECTION,
getType());
Iterator<ClassOrInterfaceTypeDetails> it = allControllers.iterator();
String referencedPath = "";
ClassOrInterfaceTypeDetails referencedController = null;
while (it.hasNext()) {
ClassOrInterfaceTypeDetails controller = it.next();
ControllerAnnotationValues values = new ControllerAnnotationValues(controller);
AnnotationMetadata controllerAnnotation =
controller.getAnnotation(RooJavaType.ROO_CONTROLLER);
AnnotationAttributeValue<String> prefixAttr = controllerAnnotation.getAttribute("pathPrefix");
if (StringUtils.equals(values.getPathPrefix(), controllerPrefix)) {
// Get path
referencedPath = getControllerOperations().getBaseUrlForController(controller);
referencedController = controller;
// Get target entity metadata to get identifier field
ClassOrInterfaceTypeDetails relatedEntityCid =
getTypeLocationService().getTypeDetails(typeDetails.getType());
JpaEntityMetadata relatedEntityMetadata =
getMetadataService().get(JpaEntityMetadata.createIdentifier(relatedEntityCid));
fieldItem.addConfigurationElement("identifierField", relatedEntityMetadata
.getCurrentIndentifierField().getFieldName().getSymbolName());
break;
}
}
if (referencedController == null) {
return false;
}
fieldItem.addConfigurationElement("referencedPath", referencedPath);
fieldItem.addConfigurationElement("referencedController", referencedController);
return true;
}
@Override
public Map<String, String> getI18nLabels(MemberDetails entityMemberDetails, JavaType entity,
JpaEntityMetadata entityMetadata, ControllerMetadata controllerMetadata, String module,
ViewContext<T> ctx) {
final Map<String, String> properties = new LinkedHashMap<String, String>();
final String entityName = entity.getSimpleTypeName();
properties.put(buildLabel(entityName), new JavaSymbolName(entity.getSimpleTypeName()
.toLowerCase()).getReadableSymbolName());
final String pluralResourceId = buildLabel(entity.getSimpleTypeName(), "plural");
final String plural = getPluralService().getPlural(entity);
properties.put(pluralResourceId, new JavaSymbolName(plural).getReadableSymbolName());
final List<FieldMetadata> javaTypePersistenceMetadataDetails =
getPersistenceMemberLocator().getIdentifierFields(entity);
if (!javaTypePersistenceMetadataDetails.isEmpty()) {
for (final FieldMetadata idField : javaTypePersistenceMetadataDetails) {
properties.put(buildLabel(entityName, idField.getFieldName().getSymbolName()), idField
.getFieldName().getReadableSymbolName());
}
}
for (final FieldMetadata field : entityMemberDetails.getFields()) {
final String fieldResourceId = buildLabel(entityName, field.getFieldName().getSymbolName());
properties.put(fieldResourceId, field.getFieldName().getReadableSymbolName());
// ROO-3865: Add suport for enum internationalization
// If field is enum, add each enum value as label with proper format
JavaType fieldType = field.getFieldType();
final ClassOrInterfaceTypeDetails javaTypeDetails =
getTypeLocationService().getTypeDetails(fieldType);
if (javaTypeDetails != null
&& javaTypeDetails.getPhysicalTypeCategory().equals(PhysicalTypeCategory.ENUMERATION)) {
List<JavaSymbolName> enumConstants = javaTypeDetails.getEnumConstants();
for (JavaSymbolName constant : enumConstants) {
String enumLabelCode =
XmlUtils.convertId("enum.".concat(fieldType.getSimpleTypeName()).concat(".")
.concat(constant.getSymbolName()));
String enumLabelValue = StringUtils.capitalize(constant.getSymbolName().toLowerCase());
properties.put(enumLabelCode, enumLabelValue);
}
}
// Add related entity fields
if (field.getFieldType().getFullyQualifiedTypeName().equals(Set.class.getName())
|| field.getFieldType().getFullyQualifiedTypeName().equals(List.class.getName())) {
// Getting inner type
JavaType referencedEntity = field.getFieldType().getBaseType();
ClassOrInterfaceTypeDetails referencedEntityDetails =
getTypeLocationService().getTypeDetails(referencedEntity);
if (referencedEntityDetails != null
&& referencedEntityDetails.getAnnotation(RooJavaType.ROO_JPA_ENTITY) != null) {
for (final FieldMetadata referencedEntityField : getMemberDetailsScanner()
.getMemberDetails(this.getClass().getName(), referencedEntityDetails).getFields()) {
final String referenceEntityFieldResourceId =
buildLabel(entityName, field.getFieldName().getSymbolName(), referencedEntityField
.getFieldName().getSymbolName());
properties.put(referenceEntityFieldResourceId, referencedEntityField.getFieldName()
.getReadableSymbolName());
}
}
}
}
// ROO-3868: Add label for localized SpEL to show entity in presentation view
// Check for @EntityFormat at entity type-level
addI18nLabelsFromEntityFormatExpressions(properties,
getTypeLocationService().getTypeDetails(entity));
// Check for @EntityFormat at all existing projections for this entity
Collection<ClassOrInterfaceTypeDetails> entityProjectionsForEntity =
getEntityProjectionLocator().getEntityProjectionsForEntity(entity);
for (ClassOrInterfaceTypeDetails details : entityProjectionsForEntity) {
addI18nLabelsFromEntityFormatExpressions(properties, details);
}
return properties;
}
/**
* Locates `@EntityFormat` annotations at type-level and relation fields and
* adds a new message to provided map if the annotation has the `message`
* attribute.
*
* @param properties the Map<String, String> to be added to message bundles.
* @param details the ClassOrInterfaceTypeDetails to check.
*/
private void addI18nLabelsFromEntityFormatExpressions(final Map<String, String> properties,
ClassOrInterfaceTypeDetails details) {
// Get all projection member details
if (details != null) {
MemberDetails memberDetails =
getMemberDetailsScanner().getMemberDetails(this.getClass().getName(), details);
// Search for @EntityFormat at type-level
AnnotationMetadata entityFormatTypeLevelAnnotation =
memberDetails.getAnnotation(SpringletsJavaType.SPRINGLETS_ENTITY_FORMAT);
if (entityFormatTypeLevelAnnotation != null) {
AnnotationAttributeValue<Object> messageAttribute =
entityFormatTypeLevelAnnotation.getAttribute("message");
if (messageAttribute != null) {
properties.put((String) messageAttribute.getValue(), "TO BE MODIFIED BY DEVELOPER");
}
}
// Search for @EntityFormat at relation fields
for (FieldMetadata field : memberDetails.getFields()) {
AnnotationMetadata entityFormatAnnotation =
field.getAnnotation(SpringletsJavaType.SPRINGLETS_ENTITY_FORMAT);
if (entityFormatAnnotation != null) {
AnnotationAttributeValue<Object> messageAttribute =
entityFormatAnnotation.getAttribute("message");
if (messageAttribute != null) {
properties.put((String) messageAttribute.getValue(), "TO BE MODIFIED BY DEVELOPER");
}
}
}
}
}
/**
* This method obtains all necessary information about fields from each
* one-to-one referenced entity.
*
* @param fieldList the List with main entity fields.
* @param checkMaxFields whether field number is restricted.
* @param ctx the context with all the necessary information for view
* generation.
* @param suffixId the suffix for items.
* @param compositeReferencedFields the entity fields with one-to-one and
* composite relation
* @param parentEntity the parent entity type
* @return the Map where the key is the current entity field and the value is
* the referenced entity editable field list.
*/
private Map<String, List<FieldItem>> getRelationFieldItems(List<FieldMetadata> fieldList,
boolean checkMaxFields, ViewContext<T> ctx, String suffixId,
List<String> compositeReferencedFields, JpaEntityMetadata parentEntity) {
// Create the Map to return
Map<String, List<FieldItem>> relationFieldsMap = new HashMap<String, List<FieldItem>>();
for (FieldMetadata entityField : fieldList) {
// If one-to-one composition field, get referenced entity fields instead
// of parent field
if (compositeReferencedFields.contains(entityField.getFieldName().getSymbolName())) {
// Create the list for related entity fields
List<FieldItem> referencedEntityFieldItems = new ArrayList<FieldItem>();
// Get referenced entity fields
List<FieldMetadata> referencedEntityFields =
getMemberDetailsScanner().getMemberDetails(this.getClass().getName(),
getTypeLocationService().getTypeDetails(entityField.getFieldType())).getFields();
List<FieldMetadata> referencedEntityEditableFields =
getEditableFields(referencedEntityFields);
// Add referenced entity field view items
for (FieldMetadata referencedEntityField : referencedEntityEditableFields) {
// If references the parent entity, don't add it
if (referencedEntityField.getFieldType().equals(parentEntity.getAnnotatedEntity())) {
continue;
}
FieldItem fieldItem =
createFieldItem(parentEntity, referencedEntityField, entityField.getFieldType()
.getSimpleTypeName(), suffixId, ctx, entityField.getFieldName().getSymbolName());
if (fieldItem != null) {
referencedEntityFieldItems.add(fieldItem);
}
if (referencedEntityFieldItems.size() >= MAX_FIELDS_TO_ADD && checkMaxFields) {
break;
}
}
// Add entity to referenced entities map
if (!relationFieldsMap.containsKey(entityField.getFieldName().getSymbolName())) {
relationFieldsMap.put(entityField.getFieldName().getSymbolName(),
referencedEntityFieldItems);
}
}
}
return relationFieldsMap;
}
/**
* Checks if entity has child one-to-one composition relations and gets
* referenced fields info for adding it to the view context.
*
* @param entityMetadata the JpaEntityMetadata from parent entity
* @param entityDetails the MemeberDetails from parent entity
* @param ctx the ViewContext of view to create
* @return a Map<String, List<FieldItem>> where the keys are the referenced
* field names on parent entity and the values are the referenced
* entity fields info to add to ViewContext
*/
private Map<String, List<FieldItem>> manageChildcompositionFields(
JpaEntityMetadata entityMetadata, MemberDetails entityDetails, ViewContext<T> ctx) {
// Manage referenced fields from one-to-one composite relations
Map<String, RelationInfo> relationInfos = entityMetadata.getRelationInfos();
List<String> compositeReferencedFields = new ArrayList<String>();
for (Entry<String, RelationInfo> entry : relationInfos.entrySet()) {
if (entry.getValue().type.name().equals(JpaRelationType.COMPOSITION.name())
&& entry.getValue().cardinality.name().equals(Cardinality.ONE_TO_ONE.name())) {
compositeReferencedFields.add(entry.getKey());
}
}
Map<String, List<FieldItem>> compositeRelationFields =
getRelationFieldItems(getEditableFields(entityDetails.getFields()), false, ctx,
FIELD_SUFFIX, compositeReferencedFields, entityMetadata);
return compositeRelationFields;
}
/**
* Builds the label of the specified field by joining its names and adding it
* to the entity label
*
* @param entity the entity name
* @param fieldNames list of fields
* @return label
*/
private static String buildLabel(String entityName, String... fieldNames) {
String label = XmlUtils.convertId("label." + entityName.toLowerCase());
for (String fieldName : fieldNames) {
label = XmlUtils.convertId(label.concat(".").concat(fieldName.toLowerCase()));
}
return label;
}
/**
* This method load the provided file and get its content in String format.
* After that, uses parse method to generate a valid DOC object.
*
* @param path
* @return
*/
protected DOC loadExistingDoc(String path) {
String content = "";
try {
// Load file and get STRING content
content = FileUtils.readFileToString(new File(path));
} catch (IOException e) {
throw new RuntimeException(String.format("ERROR: Error trying to load existing doc %s", path));
}
// Parse String content to obtain the same type of object
return parse(content);
}
/**
* This method check if the provided viewPath file exists
*
* @param viewName
* @return true if exists the provided view path
*/
protected boolean existsFile(String viewPath) {
return getFileManager().exists(viewPath);
}
@Override
public ViewContext<T> createViewContext(final ControllerMetadata controllerMetadata,
final JavaType entity, final JpaEntityMetadata entityMetadata, T viewMetadata) {
ViewContext<T> ctx = new ViewContext<T>();
ctx.setControllerPath(controllerMetadata.getPath());
ctx.setProjectName(getProjectOperations().getProjectName(""));
ctx.setVersion(getProjectOperations().getPomFromModuleName("").getVersion());
ctx.setEntityName(entity.getSimpleTypeName());
ctx.setModelAttribute(StringUtils.uncapitalize(entity.getSimpleTypeName()));
ctx.setModelAttributeName(StringUtils.uncapitalize(entity.getSimpleTypeName()));
ctx.setIdentifierField(entityMetadata.getCurrentIndentifierField().getFieldName()
.getSymbolName());
ctx.setViewMetadata(viewMetadata);
ctx.setControllerMetadata(controllerMetadata);
return ctx;
}
// Getting OSGi Services
protected FileManager getFileManager() {
return serviceInstaceManager.getServiceInstance(this, FileManager.class);
}
protected TypeLocationService getTypeLocationService() {
return serviceInstaceManager.getServiceInstance(this, TypeLocationService.class);
}
protected PersistenceMemberLocator getPersistenceMemberLocator() {
return serviceInstaceManager.getServiceInstance(this, PersistenceMemberLocator.class);
}
protected MemberDetailsScanner getMemberDetailsScanner() {
return serviceInstaceManager.getServiceInstance(this, MemberDetailsScanner.class);
}
protected I18nOperations getI18nOperations() {
return serviceInstaceManager.getServiceInstance(this, I18nOperations.class);
}
protected PluralService getPluralService() {
return serviceInstaceManager.getServiceInstance(this, PluralService.class);
}
protected ControllerLocator getControllerLocator() {
return serviceInstaceManager.getServiceInstance(this, ControllerLocator.class);
}
protected RepositoryJpaLocator getRepositoryJpaLocator() {
return serviceInstaceManager.getServiceInstance(this, RepositoryJpaLocator.class);
}
protected ControllerOperations getControllerOperations() {
return serviceInstaceManager.getServiceInstance(this, ControllerOperations.class);
}
protected ProjectOperations getProjectOperations() {
return serviceInstaceManager.getServiceInstance(this, ProjectOperations.class);
}
protected EntityProjectionLocator getEntityProjectionLocator() {
return serviceInstaceManager.getServiceInstance(this, EntityProjectionLocator.class);
}
}