Java tutorial
/* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.ocs.dynamo.domain.model.impl; import java.beans.PropertyDescriptor; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javax.persistence.ElementCollection; import javax.persistence.Embedded; import javax.persistence.Id; import javax.persistence.Lob; import javax.persistence.ManyToMany; import javax.persistence.OneToMany; import javax.persistence.Temporal; import javax.persistence.TemporalType; import javax.validation.constraints.AssertFalse; import javax.validation.constraints.AssertTrue; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import org.apache.commons.lang.StringUtils; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import com.google.common.collect.Sets; import com.ocs.dynamo.domain.AbstractEntity; import com.ocs.dynamo.domain.model.AttributeDateType; import com.ocs.dynamo.domain.model.AttributeModel; import com.ocs.dynamo.domain.model.AttributeSelectMode; import com.ocs.dynamo.domain.model.AttributeTextFieldMode; import com.ocs.dynamo.domain.model.AttributeType; import com.ocs.dynamo.domain.model.EntityModel; import com.ocs.dynamo.domain.model.EntityModelFactory; import com.ocs.dynamo.domain.model.VisibilityType; import com.ocs.dynamo.domain.model.annotation.Attribute; import com.ocs.dynamo.domain.model.annotation.AttributeGroup; import com.ocs.dynamo.domain.model.annotation.AttributeGroups; import com.ocs.dynamo.domain.model.annotation.AttributeOrder; import com.ocs.dynamo.domain.model.annotation.Model; import com.ocs.dynamo.domain.validator.Email; import com.ocs.dynamo.exception.OCSRuntimeException; import com.ocs.dynamo.service.MessageService; import com.ocs.dynamo.utils.ClassUtils; import com.ocs.dynamo.utils.SystemPropertyUtils; import com.vaadin.server.VaadinSession; import com.vaadin.ui.DefaultFieldFactory; /** * Implementation of the entity model factory - creates models that hold metadata about an entity * * @author bas.rutten */ public class EntityModelFactoryImpl implements EntityModelFactory { private static final String PLURAL_POSTFIX = "s"; private static final String CLASS = "class"; private static final String VERSION = "version"; private static final int RECURSIVE_MODEL_DEPTH = 3; @Autowired(required = false) private MessageService messageService; private ConcurrentMap<String, EntityModel<?>> cache = new ConcurrentHashMap<String, EntityModel<?>>(); private ConcurrentMap<String, Class<?>> alreadyProcessed = new ConcurrentHashMap<String, Class<?>>(); /** * Constructs an attribute model for a property * * @param descriptor * the property descriptor * @param entityModel * the entity model * @param parentClass * the type of the direct parent of the attribute (relevant in case of embedded * attributes) * @param nested * whether this is a nested attribute * @param prefix * the prefix to apply to the attribute name * @return */ private <T> List<AttributeModel> constructAttributeModel(PropertyDescriptor descriptor, EntityModelImpl<T> entityModel, Class<?> parentClass, boolean nested, String prefix) { List<AttributeModel> result = new ArrayList<AttributeModel>(); // validation methods annotated with @AssertTrue or @AssertFalse have to // be ignored String fieldName = descriptor.getName(); AssertTrue assertTrue = ClassUtils.getAnnotation(entityModel.getEntityClass(), fieldName, AssertTrue.class); AssertFalse assertFalse = ClassUtils.getAnnotation(entityModel.getEntityClass(), fieldName, AssertFalse.class); if (assertTrue == null && assertFalse == null) { AttributeModelImpl model = new AttributeModelImpl(); model.setEntityModel(entityModel); String displayName = DefaultFieldFactory.createCaptionByPropertyId(fieldName); // first, set the defaults model.setDisplayName(displayName); model.setDescription(displayName); model.setPrompt(displayName); model.setMainAttribute(descriptor.isPreferred()); model.setSearchable(descriptor.isPreferred()); model.setName((prefix == null ? "" : (prefix + ".")) + fieldName); model.setImage(false); model.setReadOnly(descriptor.isHidden()); model.setSortable(true); model.setComplexEditable(false); model.setPrecision(SystemPropertyUtils.getDefaultDecimalPrecision()); model.setSearchCaseSensitive(false); model.setSearchPrefixOnly(false); model.setUrl(false); model.setUseThousandsGrouping(true); Id idAttr = ClassUtils.getAnnotation(entityModel.getEntityClass(), fieldName, Id.class); if (idAttr != null) { entityModel.setIdAttributeModel(model); // the ID column is hidden. details collections are also hidden // by default model.setVisible(false); } else { model.setVisible(true); } model.setType(descriptor.getPropertyType()); // determine the possible date type model.setDateType(determineDateType(model.getType(), entityModel.getEntityClass(), fieldName)); // determine default display format model.setDisplayFormat(determineDefaultDisplayFormat(model.getType(), entityModel.getEntityClass(), fieldName, model.getDateType())); // determine if the attribute is required based on the @NotNull // annotation NotNull notNull = ClassUtils.getAnnotation(entityModel.getEntityClass(), fieldName, NotNull.class); model.setRequired(notNull != null); model.setAttributeType(determineAttributeType(parentClass, model)); // minimum and maximum length based on the @Size annotation Size size = ClassUtils.getAnnotation(entityModel.getEntityClass(), fieldName, Size.class); if (AttributeType.BASIC.equals(model.getAttributeType()) && size != null) { model.setMaxLength(size.max()); model.setMinLength(size.min()); } setNestedEntityModel(model); // only basic attributes are shown in the table by default model.setVisibleInTable( !nested && model.isVisible() && (AttributeType.BASIC.equals(model.getAttributeType()))); if (getMessageService() != null) { model.setTrueRepresentation(getMessageService().getMessage("ocs.true")); model.setFalseRepresentation(getMessageService().getMessage("ocs.false")); } // by default, use a combo box to look up model.setSelectMode(AttributeSelectMode.COMBO); model.setTextFieldMode(AttributeTextFieldMode.TEXTFIELD); model.setSearchSelectMode(AttributeSelectMode.COMBO); // is the field an email field? Email email = ClassUtils.getAnnotation(entityModel.getEntityClass(), fieldName, Email.class); model.setEmail(email != null); // override the defaults with annotation values setAnnotationOverrides(parentClass, model, descriptor, nested); // override any earlier version with message bundle contents setMessageBundleOverrides(entityModel, model); if (!model.isEmbedded()) { result.add(model); } else { // an embedded object is not directly added. Instead, its child // properties are added as attributes if (model.getType().equals(entityModel.getEntityClass())) { throw new IllegalStateException("Embedding a class in itself is not allowed"); } PropertyDescriptor[] embeddedDescriptors = BeanUtils.getPropertyDescriptors(model.getType()); for (PropertyDescriptor embeddedDescriptor : embeddedDescriptors) { String name = embeddedDescriptor.getName(); if (!skipAttribute(name)) { List<AttributeModel> embeddedModels = constructAttributeModel(embeddedDescriptor, entityModel, model.getType(), nested, model.getName()); result.addAll(embeddedModels); } } } } return result; } /** * Constructs the model for an entity * * @param entityClass * the class of the entity * @return */ @SuppressWarnings("unchecked") private synchronized <T> EntityModel<T> constructModel(String reference, Class<T> entityClass) { EntityModel<T> result = (EntityModel<T>) cache.get(reference); if (result == null) { boolean nested = reference.indexOf('.') > 0; // construct the basic model EntityModelImpl<T> model = constructModelInner(entityClass, reference); Map<String, String> attributeGroupMap = determineAttributeGroupMapping(model, entityClass); model.addAttributeGroup(EntityModel.DEFAULT_GROUP); alreadyProcessed.put(reference, entityClass); // keep track of main attributes - if no main attribute is defined, mark // either the // first string attribute or the first searchable attribute as the main boolean mainAttributeFound = false; AttributeModel firstStringAttribute = null; AttributeModel firstSearchableAttribute = null; PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(entityClass); // create attribute models for all attributes List<AttributeModel> tempModelList = new ArrayList<>(); for (PropertyDescriptor descriptor : descriptors) { if (!skipAttribute(descriptor.getName())) { List<AttributeModel> attributeModels = constructAttributeModel(descriptor, model, model.getEntityClass(), nested, null); for (AttributeModel attributeModel : attributeModels) { // check if the main attribute has been found mainAttributeFound |= attributeModel.isMainAttribute(); // also keep track of the first string property... if (firstStringAttribute == null && String.class.equals(attributeModel.getType())) { firstStringAttribute = attributeModel; } // ... and the first searchable property if (firstSearchableAttribute == null && attributeModel.isSearchable()) { firstSearchableAttribute = attributeModel; } tempModelList.add(attributeModel); } } } // assign ordering and sort determineAttributeOrder(entityClass, reference, tempModelList); Collections.sort(tempModelList); // add the attributes to the model for (AttributeModel attributeModel : tempModelList) { // determine the attribute group name String group = attributeGroupMap.get(attributeModel.getName()); if (StringUtils.isEmpty(group)) { group = EntityModel.DEFAULT_GROUP; } model.addAttributeModel(group, attributeModel); } if (!mainAttributeFound && !nested) { if (firstStringAttribute != null) { firstStringAttribute.setMainAttribute(true); } else if (firstSearchableAttribute != null) { firstSearchableAttribute.setMainAttribute(true); } } String sortOrder = null; Model annot = entityClass.getAnnotation(Model.class); if (annot != null && !StringUtils.isEmpty(annot.sortOrder())) { sortOrder = annot.sortOrder(); } String sortOrderMsg = getEntityMessage(reference, EntityModel.SORT_ORDER); if (!StringUtils.isEmpty(sortOrderMsg)) { sortOrder = sortOrderMsg; } setSortOrder(model, sortOrder); cache.put(reference, model); result = model; } return result; } /** * @param entityClass * @param reference * @return */ private <T> EntityModelImpl<T> constructModelInner(Class<T> entityClass, String reference) { String displayName = DefaultFieldFactory.createCaptionByPropertyId(entityClass.getSimpleName()); String displayNamePlural = displayName + PLURAL_POSTFIX; String description = displayName; String selectDisplayProperty = null; Model annot = entityClass.getAnnotation(Model.class); if (annot != null) { if (!StringUtils.isEmpty(annot.displayName())) { displayName = annot.displayName(); } if (!StringUtils.isEmpty(annot.displayNamePlural())) { displayNamePlural = annot.displayNamePlural(); } if (!StringUtils.isEmpty(annot.description())) { description = annot.description(); } if (!StringUtils.isEmpty(annot.displayProperty())) { selectDisplayProperty = annot.displayProperty(); } } // override using message bundle String displayMsg = getEntityMessage(reference, EntityModel.DISPLAY_NAME); if (!StringUtils.isEmpty(displayMsg)) { displayName = displayMsg; } String pluralMsg = getEntityMessage(reference, EntityModel.DISPLAY_NAME_PLURAL); if (!StringUtils.isEmpty(pluralMsg)) { displayNamePlural = pluralMsg; } String descriptionMsg = getEntityMessage(reference, EntityModel.DESCRIPTION); if (!StringUtils.isEmpty(descriptionMsg)) { description = descriptionMsg; } String selectDisplayPropertyMsg = getEntityMessage(reference, EntityModel.DISPLAY_PROPERTY); if (!StringUtils.isEmpty(selectDisplayPropertyMsg)) { selectDisplayProperty = selectDisplayPropertyMsg; } return new EntityModelImpl<>(entityClass, reference, displayName, displayNamePlural, description, selectDisplayProperty); } /** * Construct a mapping from attribute to its corresponding attribute group * * @param entityClass * @return */ private <T> Map<String, String> determineAttributeGroupMapping(EntityModelImpl<T> model, Class<T> entityClass) { Map<String, String> result = new HashMap<>(); AttributeGroups groups = entityClass.getAnnotation(AttributeGroups.class); if (groups != null) { for (AttributeGroup g : groups.attributeGroups()) { model.addAttributeGroup(g.displayName()); for (String s : g.attributeNames()) { result.put(s, g.displayName()); } } } else { AttributeGroup group = entityClass.getAnnotation(AttributeGroup.class); if (group != null) { model.addAttributeGroup(group.displayName()); for (String s : group.attributeNames()) { result.put(s, group.displayName()); } } } // look for message bundle overwrites int i = 1; if (messageService != null) { String groupName = messageService.getEntityMessage(model.getReference(), EntityModel.ATTRIBUTE_GROUP + "." + i + "." + EntityModel.DISPLAY_NAME); while (groupName != null) { String attributeNames = messageService.getEntityMessage(model.getReference(), EntityModel.ATTRIBUTE_GROUP + "." + i + "." + EntityModel.ATTRIBUTE_NAMES); if (attributeNames != null) { model.addAttributeGroup(groupName); for (String s : attributeNames.split(",")) { result.put(s, groupName); } } i++; groupName = messageService.getEntityMessage(model.getReference(), EntityModel.ATTRIBUTE_GROUP + "." + i + "." + EntityModel.DISPLAY_NAME); } } return result; } /** * Determines the order of the attributes - this will first pick up any attributes that are * mentioned in the @AttributeOrder annotation (in the order in which they occur) and then add * any attributes that are not explicitly mentioned * * @param entityClass * @param reference * @param attributeModels * @return */ private <T> void determineAttributeOrder(Class<T> entityClass, String reference, List<AttributeModel> attributeModels) { List<String> explicitAttributeNames = new ArrayList<>(); List<String> additionalNames = new ArrayList<>(); // read ordering from the annotation (if present) AttributeOrder orderAnnot = entityClass.getAnnotation(AttributeOrder.class); if (orderAnnot != null) { explicitAttributeNames = Arrays.asList(orderAnnot.attributeNames()); } // overwrite by message bundle (if present) String msg = messageService == null ? null : messageService.getEntityMessage(reference, EntityModel.ATTRIBUTE_ORDER); if (msg != null) { explicitAttributeNames = Arrays.asList(msg.replaceAll("\\s+", "").split(",")); } for (AttributeModel attributeModel : attributeModels) { String name = attributeModel.getName(); if (!skipAttribute(name) && !explicitAttributeNames.contains(name)) { additionalNames.add(name); } } // add the explicitly named attributes List<String> result = new ArrayList<>(explicitAttributeNames); // add the additional unmentioned attributes result.addAll(additionalNames); // loop over the attributes and set the orders int i = 0; for (String attributeName : result) { AttributeModel am = null; for (AttributeModel m : attributeModels) { if (m.getName().equals(attributeName)) { am = m; break; } } if (am != null) { ((AttributeModelImpl) am).setOrder(i); i++; } else { throw new OCSRuntimeException("Attribute " + attributeName + " is not known"); } } } /** * Determines the attribute type of an attribute * * @param model * the model representation of the attribute * @return */ private AttributeType determineAttributeType(Class<?> parentClass, AttributeModelImpl model) { AttributeType result = null; String name = model.getName(); int p = name.lastIndexOf("."); if (p > 0) { name = name.substring(p + 1); } if (!BeanUtils.isSimpleValueType(model.getType())) { // No relation type set in view model definition, hence derive // defaults Embedded embedded = ClassUtils.getAnnotation(parentClass, name, Embedded.class); Attribute attribute = ClassUtils.getAnnotation(parentClass, name, Attribute.class); if (embedded != null) { result = AttributeType.EMBEDDED; } else if (Collection.class.isAssignableFrom(model.getType())) { if (attribute != null && attribute.memberType() != null && !attribute.memberType().equals(Object.class)) { // if a member type is explicitly set, use that type result = AttributeType.DETAIL; model.setMemberType(attribute.memberType()); } else if (ClassUtils.getAnnotation(parentClass, name, ManyToMany.class) != null || ClassUtils.getAnnotation(parentClass, name, OneToMany.class) != null) { result = AttributeType.DETAIL; model.setMemberType(ClassUtils.getResolvedType(parentClass, model.getName(), 0)); } else if (ClassUtils.getAnnotation(parentClass, name, ElementCollection.class) != null) { result = AttributeType.ELEMENT_COLLECTION; model.setMemberType(ClassUtils.getResolvedType(parentClass, model.getName(), 0)); } else if (AbstractEntity.class.isAssignableFrom(model.getType())) { // not a collection but a reference to another object result = AttributeType.MASTER; } } else if (model.getType().isArray()) { // a byte array with the @Lob annotation is transformed to a // @Lob field Lob lob = ClassUtils.getAnnotation(parentClass, name, Lob.class); if (lob != null) { result = AttributeType.LOB; } } else { // not a collection but a reference to another object result = AttributeType.MASTER; } } else { // simple attribute type result = AttributeType.BASIC; } return result; } private <T> AttributeDateType determineDateType(Class<?> modelType, Class<T> entityClass, String fieldName) { // set the date type if (Date.class.equals(modelType)) { Temporal temporal = ClassUtils.getAnnotation(entityClass, fieldName, Temporal.class); Attribute attribute = ClassUtils.getAnnotation(entityClass, fieldName, Attribute.class); final boolean customAttributeDateTypeSet = attribute != null && attribute.dateType() != AttributeDateType.INHERIT; if (temporal != null && !customAttributeDateTypeSet) { // use the @Temporal annotation when available and not overridden by Attribute return translateDateType(temporal.value()); } else { if (customAttributeDateTypeSet) { return attribute.dateType(); } // by default use date return AttributeDateType.DATE; } } return null; } /** * Determines the display format on a property * * @param type * the java type * @param entityClass * the class on which the property is defined * @param fieldName * @param dateType * @return */ private String determineDefaultDisplayFormat(Class<?> type, Class<?> entityClass, String fieldName, AttributeDateType dateType) { String format = null; if (Date.class.isAssignableFrom(type)) { switch (dateType) { case TIME: format = SystemPropertyUtils.getDefaultTimeFormat(); break; case TIMESTAMP: format = SystemPropertyUtils.getDefaultDateTimeFormat(); break; default: // by default use a date format = SystemPropertyUtils.getDefaultDateFormat(); } } return format; } /** * Retrieves a message relating to an attribute from the message bundle * * @param model * the entity model * @param attributeModel * the attribute model * @param propertyName * the name of the property * @return */ private <T> String getAttributeMessage(EntityModel<T> model, AttributeModel attributeModel, String propertyName) { if (messageService != null) { return messageService.getAttributeMessage(model.getReference(), attributeModel, propertyName); } return null; } /** * Retrieves a message relating to an entity from the message bundle * * @param reference * @param propertyName * @return */ private String getEntityMessage(String reference, String propertyName) { if (messageService != null) { return messageService.getEntityMessage(reference, propertyName); } return null; } protected Locale getLocale() { VaadinSession session = VaadinSession.getCurrent(); if (session != null) { return session.getLocale(); } return Locale.getDefault(); } @Override public MessageService getMessageService() { return messageService; } @Override public <T> EntityModel<T> getModel(Class<T> entityClass) { return getModel(entityClass.getSimpleName(), entityClass); } @Override @SuppressWarnings({ "unchecked" }) public <T> EntityModel<T> getModel(String reference, Class<T> entityClass) { EntityModel<T> model = null; if (!StringUtils.isEmpty(reference) && entityClass != null) { model = (EntityModel<T>) cache.get(reference); if (model == null) { model = constructModel(reference, entityClass); } } return model; } /** * Check if a certain entity model has already been processed * * @param type * the type of the entity * @param reference * the reference to the entity * @return */ private boolean hasEntityModel(Class<?> type, String reference) { for (Entry<String, Class<?>> e : alreadyProcessed.entrySet()) { if (reference.startsWith(e.getKey()) && e.getValue().equals(type)) { // only check for starting reference in order to prevent recursive looping between // two-sided relations return true; } } return false; } @Override public boolean hasModel(String reference) { return cache.containsKey(reference); } /** * Overwrite the default values with annotation values * * @param parentClass * @param model * @param descriptor */ private void setAnnotationOverrides(Class<?> parentClass, AttributeModelImpl model, PropertyDescriptor descriptor, boolean nested) { Attribute attribute = ClassUtils.getAnnotation(parentClass, descriptor.getName(), Attribute.class); // overwrite with annotation values if (attribute != null) { if (!StringUtils.isEmpty(attribute.displayName())) { model.setDisplayName(attribute.displayName()); // by default, set prompt and description to the display // name as // well - // they are overwritten in the following code if they are // explicitly set model.setPrompt(attribute.displayName()); model.setDescription(attribute.displayName()); } if (!StringUtils.isEmpty(attribute.defaultValue())) { String defaultValue = attribute.defaultValue(); setDefaultValue(model, defaultValue); } if (!StringUtils.isEmpty(attribute.description())) { model.setDescription(attribute.description()); } if (!StringUtils.isEmpty(attribute.displayFormat())) { model.setDisplayFormat(attribute.displayFormat()); } if (!StringUtils.isEmpty(attribute.prompt())) { model.setPrompt(attribute.prompt()); } if (attribute.readOnly()) { model.setReadOnly(true); } // set visibility (but not for nested attributes - these are hidden by default) if (attribute.visible() != null && !VisibilityType.INHERIT.equals(attribute.visible()) && !nested) { model.setVisible(VisibilityType.SHOW.equals(attribute.visible())); model.setVisibleInTable(model.isVisible()); } if (attribute.searchable() && !nested) { model.setSearchable(true); } if (!attribute.sortable()) { model.setSortable(false); } if (attribute.main() && !nested) { model.setMainAttribute(true); } if (attribute.showInTable() != null && !VisibilityType.INHERIT.equals(attribute.showInTable()) && !nested) { model.setVisibleInTable(VisibilityType.SHOW.equals(attribute.showInTable())); } if (attribute.complexEditable()) { model.setComplexEditable(true); } if (attribute.image()) { model.setImage(true); } if (attribute.week()) { model.setWeek(true); } if (!StringUtils.isEmpty(attribute.allowedExtensions())) { String[] extensions = attribute.allowedExtensions().split(","); Set<String> hashSet = Sets.newHashSet(extensions); model.setAllowedExtensions(hashSet); } if (!StringUtils.isEmpty(attribute.trueRepresentation())) { model.setTrueRepresentation(attribute.trueRepresentation()); } if (!StringUtils.isEmpty(attribute.falseRepresentation())) { model.setFalseRepresentation(attribute.falseRepresentation()); } if (attribute.detailFocus()) { model.setDetailFocus(true); } if (attribute.percentage()) { model.setPercentage(true); } if (attribute.currency()) { model.setCurrency(true); } if (attribute.dateType() != null && !AttributeDateType.INHERIT.equals(attribute.dateType())) { model.setDateType(attribute.dateType()); } // overrule select mode - by default, this overrules the search select mode as well if (attribute.selectMode() != null && !AttributeSelectMode.INHERIT.equals(attribute.selectMode())) { model.setSelectMode(attribute.selectMode()); model.setSearchSelectMode(attribute.selectMode()); } // set multiple search if (attribute.multipleSearch()) { model.setMultipleSearch(true); // by default, use a fancy list for multiple search model.setSearchSelectMode(AttributeSelectMode.FANCY_LIST); } if (!AttributeSelectMode.INHERIT.equals(attribute.searchSelectMode())) { model.setSearchSelectMode(attribute.searchSelectMode()); } model.setSearchCaseSensitive(attribute.searchCaseSensitive()); model.setSearchPrefixOnly(attribute.searchPrefixOnly()); if (attribute.textFieldMode() != null && !AttributeTextFieldMode.INHERIT.equals(attribute.textFieldMode())) { model.setTextFieldMode(attribute.textFieldMode()); } if (attribute.precision() > -1) { model.setPrecision(attribute.precision()); } if (attribute.minLength() > -1) { model.setMinLength(attribute.minLength()); } if (attribute.maxLength() > -1) { model.setMaxLength(attribute.maxLength()); } if (attribute.url()) { model.setUrl(true); } if (!StringUtils.isEmpty(attribute.replacementSearchPath())) { model.setReplacementSearchPath(attribute.replacementSearchPath()); } if (!StringUtils.isEmpty(attribute.quickAddPropertyName())) { model.setQuickAddPropertyName(attribute.quickAddPropertyName()); model.setQuickAddAllowed(true); } if (attribute.required()) { model.setRequired(true); } if (!attribute.useThousandsGrouping()) { model.setUseThousandsGrouping(false); } if (attribute.searchForExactValue()) { model.setSearchForExactValue(true); } if (!StringUtils.isEmpty(attribute.fileNameProperty())) { model.setFileNameProperty(attribute.fileNameProperty()); } } } /** * Sets the default value on the attribute model (translates a String to the appropriate type) * * @param model * @param defaultValue */ @SuppressWarnings("unchecked") private void setDefaultValue(AttributeModelImpl model, String defaultValue) { if (model.getType().isEnum()) { model.setDefaultValue(Enum.valueOf(model.getType().asSubclass(Enum.class), defaultValue)); } else if (model.getType().equals(Date.class)) { SimpleDateFormat fmt = new SimpleDateFormat(model.getDisplayFormat()); try { model.setDefaultValue(fmt.parseObject(defaultValue)); } catch (ParseException e) { throw new OCSRuntimeException("Cannot parse default date value: " + defaultValue + " with format: " + model.getDisplayFormat()); } } else { model.setDefaultValue(ClassUtils.instantiateClass(model.getType(), defaultValue)); } } /** * Overwrite the values of the model with values read from the messageBundle * * @param entityModel * @param model */ private <T> void setMessageBundleOverrides(EntityModel<T> entityModel, AttributeModelImpl model) { String msg = getAttributeMessage(entityModel, model, EntityModel.DISPLAY_NAME); if (!StringUtils.isEmpty(msg)) { model.setDisplayName(msg); } msg = getAttributeMessage(entityModel, model, EntityModel.DESCRIPTION); if (!StringUtils.isEmpty(msg)) { model.setDescription(msg); } msg = getAttributeMessage(entityModel, model, EntityModel.DEFAULT_VALUE); if (!StringUtils.isEmpty(msg)) { setDefaultValue(model, msg); } msg = getAttributeMessage(entityModel, model, EntityModel.DISPLAY_FORMAT); if (!StringUtils.isEmpty(msg)) { model.setDisplayFormat(msg); } msg = getAttributeMessage(entityModel, model, EntityModel.MAIN); if (!StringUtils.isEmpty(msg)) { model.setMainAttribute(Boolean.valueOf(msg)); } msg = getAttributeMessage(entityModel, model, EntityModel.PROMPT); if (!StringUtils.isEmpty(msg)) { model.setPrompt(msg); } msg = getAttributeMessage(entityModel, model, EntityModel.READ_ONLY); if (!StringUtils.isEmpty(msg)) { model.setReadOnly(Boolean.valueOf(msg)); } msg = getAttributeMessage(entityModel, model, EntityModel.SEARCHABLE); if (!StringUtils.isEmpty(msg)) { model.setSearchable(Boolean.valueOf(msg)); } msg = getAttributeMessage(entityModel, model, EntityModel.SORTABLE); if (!StringUtils.isEmpty(msg)) { model.setSortable(Boolean.valueOf(msg)); } msg = getAttributeMessage(entityModel, model, EntityModel.VISIBLE); if (!StringUtils.isEmpty(msg)) { model.setVisible(isVisible(msg)); } msg = getAttributeMessage(entityModel, model, EntityModel.SHOW_IN_TABLE); if (!StringUtils.isEmpty(msg)) { model.setVisibleInTable(isVisible(msg)); } msg = getAttributeMessage(entityModel, model, EntityModel.COMPLEX_EDITABLE); if (!StringUtils.isEmpty(msg)) { model.setComplexEditable(Boolean.valueOf(msg)); } msg = getAttributeMessage(entityModel, model, EntityModel.IMAGE); if (!StringUtils.isEmpty(msg)) { model.setImage(Boolean.valueOf(msg)); } msg = getAttributeMessage(entityModel, model, EntityModel.WEEK); if (!StringUtils.isEmpty(msg)) { model.setWeek(Boolean.valueOf(msg)); } msg = getAttributeMessage(entityModel, model, EntityModel.ALLOWED_EXTENSIONS); if (!StringUtils.isEmpty(msg)) { String[] extensions = msg.split(","); Set<String> hashSet = Sets.newHashSet(extensions); model.setAllowedExtensions(hashSet); } msg = getAttributeMessage(entityModel, model, EntityModel.TRUE_REPRESENTATION); if (!StringUtils.isEmpty(msg)) { model.setTrueRepresentation(msg); } msg = getAttributeMessage(entityModel, model, EntityModel.FALSE_REPRESENTATION); if (!StringUtils.isEmpty(msg)) { model.setFalseRepresentation(msg); } msg = getAttributeMessage(entityModel, model, EntityModel.DETAIL_FOCUS); if (!StringUtils.isEmpty(msg)) { model.setDetailFocus(Boolean.valueOf(msg)); } msg = getAttributeMessage(entityModel, model, EntityModel.PERCENTAGE); if (!StringUtils.isEmpty(msg)) { model.setPercentage(Boolean.valueOf(msg)); } msg = getAttributeMessage(entityModel, model, EntityModel.PRECISION); if (!StringUtils.isEmpty(msg)) { model.setPercentage(Boolean.valueOf(msg)); } msg = getAttributeMessage(entityModel, model, EntityModel.EMBEDDED); if (!StringUtils.isEmpty(msg) && Boolean.valueOf(msg)) { model.setAttributeType(AttributeType.EMBEDDED); } msg = getAttributeMessage(entityModel, model, EntityModel.CURRENCY); if (!StringUtils.isEmpty(msg) && Boolean.valueOf(msg)) { model.setCurrency(Boolean.valueOf(msg)); } // set the select mode (also sets the search select mode by default) msg = getAttributeMessage(entityModel, model, EntityModel.SELECT_MODE); if (!StringUtils.isEmpty(msg) && AttributeSelectMode.valueOf(msg) != null) { model.setSelectMode(AttributeSelectMode.valueOf(msg)); model.setSearchSelectMode(AttributeSelectMode.valueOf(msg)); } // set multiple search (also overwrites the search select mode and sets it to fancy list) msg = getAttributeMessage(entityModel, model, EntityModel.MULTIPLE_SEARCH); if (!StringUtils.isEmpty(msg)) { model.setMultipleSearch(Boolean.valueOf(msg)); model.setSearchSelectMode(AttributeSelectMode.FANCY_LIST); } msg = getAttributeMessage(entityModel, model, EntityModel.SEARCH_SELECT_MODE); if (!StringUtils.isEmpty(msg)) { model.setSearchSelectMode(AttributeSelectMode.valueOf(msg)); } msg = getAttributeMessage(entityModel, model, EntityModel.DATE_TYPE); if (!StringUtils.isEmpty(msg)) { model.setDateType(AttributeDateType.valueOf(msg)); } msg = getAttributeMessage(entityModel, model, EntityModel.SEARCH_CASE_SENSITIVE); if (!StringUtils.isEmpty(msg)) { model.setSearchCaseSensitive(Boolean.valueOf(msg)); } msg = getAttributeMessage(entityModel, model, EntityModel.SEARCH_PREFIX_ONLY); if (!StringUtils.isEmpty(msg)) { model.setSearchPrefixOnly(Boolean.valueOf(msg)); } msg = getAttributeMessage(entityModel, model, EntityModel.TEXTFIELD_MODE); if (!StringUtils.isEmpty(msg)) { model.setTextFieldMode(AttributeTextFieldMode.valueOf(msg)); } msg = getAttributeMessage(entityModel, model, EntityModel.MIN_LENGTH); if (!StringUtils.isEmpty(msg)) { model.setMinLength(Integer.parseInt(msg)); } msg = getAttributeMessage(entityModel, model, EntityModel.MAX_LENGTH); if (!StringUtils.isEmpty(msg)) { model.setMaxLength(Integer.parseInt(msg)); } msg = getAttributeMessage(entityModel, model, EntityModel.URL); if (!StringUtils.isEmpty(msg)) { model.setUrl(Boolean.valueOf(msg)); } msg = getAttributeMessage(entityModel, model, EntityModel.REPLACEMENT_SEARCH_PATH); if (!StringUtils.isEmpty(msg)) { model.setReplacementSearchPath(msg); } msg = getAttributeMessage(entityModel, model, EntityModel.QUICK_ADD_PROPERTY); if (!StringUtils.isEmpty(msg)) { model.setQuickAddPropertyName(msg); } msg = getAttributeMessage(entityModel, model, EntityModel.THOUSANDS_GROUPING); if (!StringUtils.isEmpty(msg)) { model.setUseThousandsGrouping(Boolean.valueOf(msg)); } msg = getAttributeMessage(entityModel, model, EntityModel.SEARCH_EXACT_VALUE); if (!StringUtils.isEmpty(msg)) { model.setSearchForExactValue(Boolean.valueOf(msg)); } } /** * Calculates the entity model for a nested property, recursively up till a certain depth * * @param model * the attribute model */ private void setNestedEntityModel(AttributeModelImpl model) { EntityModel<?> em = model.getEntityModel(); if (StringUtils.countMatches(em.getReference(), ".") < RECURSIVE_MODEL_DEPTH) { Class<?> type = null; // only needed for master and detail attributes if (AttributeType.MASTER.equals(model.getAttributeType())) { type = model.getType(); } else if (AttributeType.DETAIL.equals(model.getAttributeType())) { type = model.getMemberType(); } if (type != null) { String ref = null; if (StringUtils.isEmpty(em.getReference())) { ref = em.getEntityClass() + "." + model.getName(); } else { ref = em.getReference() + "." + model.getName(); } if (type.equals(em.getEntityClass()) || !hasEntityModel(type, ref)) { EntityModel<?> nem = getModel(ref, type); model.setNestedEntityModel(nem); } } } } /** * Sets the sort order on an entity model * * @param model * the entity model * @param sortOrderMsg * the sort order from the message bundle */ private <T> void setSortOrder(EntityModelImpl<T> model, String sortOrderMsg) { if (!StringUtils.isEmpty(sortOrderMsg)) { String[] tokens = sortOrderMsg.split(","); for (String token : tokens) { String[] sd = token.trim().split(" "); if (sd.length > 0 && !StringUtils.isEmpty(sd[0]) && model.getAttributeModel(sd[0]) != null) { model.getSortOrder().put(model.getAttributeModel(sd[0]), (sd.length > 1 && ("DESC".equalsIgnoreCase(sd[1]) || "DSC".equalsIgnoreCase(sd[1]))) ? false : true); } } } } /** * Indicates whether to skip an attribute since it does not constitute an actual property but * rather a generic or technical field that all entities have * * @param name * @return */ private boolean skipAttribute(String name) { return CLASS.equals(name) || VERSION.equals(name); } /** * Translates a TemporalType enum value to an AttributeDateType * * @param type * @return */ private AttributeDateType translateDateType(TemporalType type) { switch (type) { case DATE: return AttributeDateType.DATE; case TIME: return AttributeDateType.TIME; case TIMESTAMP: return AttributeDateType.TIMESTAMP; default: return null; } } private boolean isVisible(String msg) { try { VisibilityType other = VisibilityType.valueOf(msg); return VisibilityType.SHOW.equals(other); } catch (IllegalArgumentException ex) { // do nothing } return Boolean.valueOf(msg); } }