
de.tsl2.nano.bean.def.BeanDefinition Maven / Gradle / Ivy
/*
* File: $HeadURL$
* Id : $Id$
*
* created by: Thomas Schneider
* created on: Jul 16, 2012
*
* Copyright: (c) Thomas Schneider 2012, all rights reserved
*/
package de.tsl2.nano.bean.def;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.sql.Time;
import java.text.Format;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.simpleframework.xml.Attribute;
import org.simpleframework.xml.Default;
import org.simpleframework.xml.DefaultType;
import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.ElementMap;
import org.simpleframework.xml.Namespace;
import org.simpleframework.xml.core.Commit;
import org.simpleframework.xml.core.Persist;
import de.tsl2.nano.action.CommonAction;
import de.tsl2.nano.action.IAction;
import de.tsl2.nano.action.IActivable;
import de.tsl2.nano.bean.BeanContainer;
import de.tsl2.nano.bean.BeanUtil;
import de.tsl2.nano.bean.IConnector;
import de.tsl2.nano.bean.IValueAccess;
import de.tsl2.nano.bean.ValueHolder;
import de.tsl2.nano.bean.annotation.Action;
import de.tsl2.nano.collection.CollectionUtil;
import de.tsl2.nano.core.ENV;
import de.tsl2.nano.core.IPredicate;
import de.tsl2.nano.core.ManagedException;
import de.tsl2.nano.core.Messages;
import de.tsl2.nano.core.cls.BeanAttribute;
import de.tsl2.nano.core.cls.BeanClass;
import de.tsl2.nano.core.cls.IAttribute;
import de.tsl2.nano.core.cls.PrivateAccessor;
import de.tsl2.nano.core.log.LogFactory;
import de.tsl2.nano.core.messaging.ChangeEvent;
import de.tsl2.nano.core.messaging.IListener;
import de.tsl2.nano.core.util.DateUtil;
import de.tsl2.nano.core.util.DefaultFormat;
import de.tsl2.nano.core.util.FileUtil;
import de.tsl2.nano.core.util.ListSet;
import de.tsl2.nano.core.util.StringUtil;
import de.tsl2.nano.core.util.Util;
/**
* Holds all informations to define a bean as a container of bean-attributes. Uses {@link BeanClass} and
* {@link BeanAttribute} to evaluate all attributes for a given type. usable to define a table (with columns) of beans
* of that type - like a ListDescriptor.
*
* @author Thomas Schneider
* @version $Revision$
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@Namespace(reference = "http://sourceforge.net/projects/tsl2nano ./" + BeanDefinition.BEANDEF_XSD)
@Default(value = DefaultType.FIELD, required = false)
public class BeanDefinition extends BeanClass implements IPluggable, Serializable {
static final String BEANDEF_XSD = "beandef.xsd";
/** serialVersionUID */
private static final long serialVersionUID = -1110193041263724431L;
private static final Log LOG = LogFactory.getLog(BeanDefinition.class);
/** optional filter to constrain the available attributes - in the given order */
protected transient String[] attributeFilter;
/** cached attribute values (directly a {@link LinkedHashMap} on type to get this map instance on deserialization */
@ElementMap(entry = "attribute", key = "name", attribute = true, inline = true, value = "attributeDefinition", valueType = AttributeDefinition.class, required = false)
protected LinkedHashMap> attributeDefinitions;
/** flag to define, that all attributes are evaluated and cached - for performance aspects */
transient protected boolean allDefinitionsCached = false;
/** naturalSortedAttributeNames used for performance in {@link #hasAttribute(String)} */
transient String[] naturalSortedAttributeNames;
/** optional presentation informations */
protected Presentable presentable;
/** optional helper to define presentation informations */
protected transient BeanPresentationHelper presentationHelper;
/** the beans name. used, if bean is virtual */
@Attribute
protected String name;
/**
* optional plugins - like rule names to cover properties of this beandefinition. perhaps you cover the value of
* property 'constraint.visible' with a rule defined in your specification.
*/
@ElementList (inline = true, entry = "plugin", required = false)
protected Collection> plugins;
/** should be able to create a representable string for the given instance */
protected ValueExpression valueExpression;
/**
* optional bean actions. not serialized because most actions will be defined inline - so the containing class would
* have to be serializable, too.
*/
@ElementList(inline = true, entry = "action", required = false)
protected Collection actions;
/**
* optional action names (must exist inside {@link #actions} to be started on each bean activation - normally
* through the gui implementation
*/
protected String[] activationActionNames;
/** optional attribute relations */
protected boolean isconnected;
/** optional constraints between attributes */
protected BeanValueConditionChecker crossChecker;
/** optional grouping informations */
@ElementList(inline = true, entry = "group", type = ValueGroup.class, required = false)
protected Collection valueGroups;
@Attribute(required = false)
protected boolean isNested;
/**
* this value is true, if the bean-definition was created through default algorithms - no attribute filter was
* defined.
*/
@Attribute(required = false)
protected boolean isdefault = true;
protected Extension extension;
/** used by virtual beans, having no object instance. TODO: will not work in different vm's */
@SuppressWarnings("serial")
static final Serializable UNDEFINED = new Serializable() {
@Override
public String toString() {
return "UNDEFINED";
}
};
public static final String PREFIX_VIRTUAL = "virtual";
protected static final String POSTFIX_FILE_EXT = ".xml";
private static final List virtualBeanCache = new ListSet();
private static final BeanDefinition volatileBean = new BeanDefinition(Object.class);
private static boolean usePersistentCache = ENV.get("beandef.usepersistent.cache", true);
private long seal;
/**
* This constructor is only for internal use (serialization) - don't call this constructor - use
* {@link #getBeanDefinition(Class)}, {@link #getBeanDefinition(String)} or
* {@link #getBeanDefinition(String, Class)} instead!
*/
protected BeanDefinition() {
this((Class) UNDEFINED.getClass());
}
/**
* constructor
*
* @param beanClass
*/
public BeanDefinition(Class beanClass) {
super((Class) (ENV.get("beandef.ignore.anonymous.fields", true) ? getDefiningClass(beanClass)
: beanClass));
name = beanClass == UNDEFINED.getClass() ? /*"undefined"*/StringUtil.STR_ANY : super.getName();
}
/**
* optional actions to be executed on each activation of this bean. this method will be called internally on
* selecting the bean - this is done by a specialized gui implementation. see {@link #activationActionNames}.
*
* see {@link #setActivationActionNames(String...)}. {@link #activationActionNames} and {@link #getActions()}.
* @param context
*/
public > B onActivation(Map context) {
LOG.info("onActivation of " + toString() + ": searching activation actions");
if (activationActionNames != null) {
for (int i = 0; i < activationActionNames.length; i++) {
IAction> a = getAction(activationActionNames[i]);
a.setParameter(context);
a.activate();
}
}
return (B) this;
}
/**
* callback method to be invoked by framework to do some cleanups, if this bean loses it's focus without any action.
*/
public void onDeactivation(Map context) {
}
/**
* @return Returns the activationActionNames.
*/
protected String[] getActivationActionNames() {
return activationActionNames;
}
/**
* see {@link #activationActionNames} and {@link #activationActionNames}.
*
* @param activationActionNames The activationActionNames to set.
*/
protected void setActivationActionNames(String... activationActionNames) {
this.activationActionNames = activationActionNames;
}
/**
* constrains the available attributes. the order of the filter will be used for generic attribute evaluations.
*
* @param availableAttributes only these attributes will be usable on this bean instance. see
* {@link #getAttributes(boolean)}.
*/
public void setAttributeFilter(String... availableAttributes) {
this.attributeFilter = availableAttributes;
isdefault = false;
refreshAttributeDefinitions();
setColumnDefinitionOrder(availableAttributes);
}
/**
* setColumnDefinitionOrder
*
* @param columns attribute column names
*/
public void setColumnDefinitionOrder(String[] columns) {
IAttribute a;
IPresentableColumn c;
int i = 0;
for (String name : columns) {
if ((a = getAttribute(name, false)) != null) {
if (a instanceof IAttributeDefinition) {
c = ((IAttributeDefinition) a).getColumnDefinition();
if (c instanceof ValueColumn)
((ValueColumn) c).setIndex(i++);
}
}
}
}
protected void refreshAttributeDefinitions() {
allDefinitionsCached = false;
isdefault = false;
getAttributes(false);
}
/**
* getDeclaringClass
*
* @return
*/
public Class getDeclaringClass() {
return getClazz();
}
/**
* removes the given standard-attributes from bean (given names must be contained in standard-bean-definition. not
* performance-optimized, because standard names have to be evaluated first!)
*
* @param attributeNamesToRemove attributes to remove
*/
public void removeAttributes(String... attributeNamesToRemove) {
ArrayList names = new ArrayList(Arrays.asList(getAttributeNames()));
int currentSize = names.size();
names.removeAll(Arrays.asList(attributeNamesToRemove));
if (names.size() + attributeNamesToRemove.length != currentSize) {
throw ManagedException.implementationError("not all of given attributes were removed!",
attributeNamesToRemove,
getAttributeNames());
}
setAttributeFilter(names.toArray(new String[0]));
}
/**
* @return Returns the {@link #isdefault}.
*/
public boolean isDefault() {
return isdefault;
}
public void setDefault(boolean isdefault) {
this.isdefault = isdefault;
}
/**
* {@inheritDoc}
*/
@Override
public List getAttributes(boolean readAndWriteAccess) {
if (!allDefinitionsCached) {
if (attributeFilter == null) {
List attributes = super.getAttributes(readAndWriteAccess);
attributeFilter =
new String[attributes.size() + (attributeDefinitions != null ? attributeDefinitions.size() : 0)];
int i = 0;
for (IAttribute attr : attributes) {
attributeFilter[i++] = attr.getName();
}
//the already defined virtual attributes should not be lost!
if (attributeDefinitions != null) {
Set defs = attributeDefinitions.keySet();
for (String name : defs) {
attributeFilter[i++] = name;
}
}
}
for (int i = 0; i < attributeFilter.length; i++) {
IAttributeDefinition v = getAttributeDefinitions().get(attributeFilter[i]);
if (v == null) {
v = createAttributeDefinition(attributeFilter[i]);
} else {//TODO: do we need this to reorder?
attributeDefinitions.remove(attributeFilter[i]);
}
attributeDefinitions.put(attributeFilter[i], v);
}
//remove previously created attributes that are not contained in filter
if (attributeDefinitions != null) {
Set allAttributes = attributeDefinitions.keySet();
if (allAttributes.size() != attributeFilter.length) {
List filterList = Arrays.asList(attributeFilter);
for (Iterator it = allAttributes.iterator(); it.hasNext();) {
if (!filterList.contains(it.next())) {
it.remove();
}
}
}
}
createNaturalSortedAttributeNames(attributeFilter);
allDefinitionsCached = true;
/*
* create additional attributes - ignoring the filter
*/
getPresentationHelper().defineAdditionalAttributes();
}
//don't use the generic type H (Class) to be compilable on standard jdk javac.
ArrayList attributes =
new ArrayList(getAttributeDefinitions().values());
/*
* filter the result using a default filter by the presentation helper
*/
if (ENV.get("beandef.use.beanpresentationhelper.filter", true)) {
return CollectionUtil.getFiltering(attributes, new IPredicate() {
@Override
public boolean eval(IAttribute arg0) {
return getPresentationHelper().isDefaultAttribute(arg0)
|| getValueExpression().isExpressionPart(arg0.getName());
}
});
} else {
return attributes;
}
}
protected IAttributeDefinition createAttributeDefinition(String name) {
return new AttributeDefinition(BeanAttribute.getBeanAttribute(getClazz(), name).getAccessMethod());
}
/**
* delegates to {@link #combineRelationAttributes(IAttributeDefinition)}
*/
void combineRelationAttributes(String relationAttributeName) {
combineRelationAttributes(getAttribute(relationAttributeName));
}
/**
* adds all attributes of the given bean relation (must be a bean attribute holding another bean).
*
* @param relation relation bean attribute
*/
void combineRelationAttributes(IAttributeDefinition> relation, String... attrNames) {
BeanDefinition> beanDefinition = getBeanDefinition(relation.getDeclaringClass());
List filter = Arrays.asList(attrNames);
Collection> attributes = beanDefinition.getAttributeDefinitions().values();
for (IAttributeDefinition> attr : attributes) {
if (filter.contains(attr.getName())) {
attr.setAsRelation(relation.getName() + "." + attr.getName());
addAttribute(attr);
}
}
}
/**
* a bean is virtual, if no java class definition was given. all attribute definitions must be added manual by
* calling {@link #addAttribute(Object, String, int, boolean, String, Object, String, IPresentable)} .
*
* @return true, if this bean has no class definition - the default constructor was called
*/
public boolean isVirtual() {
return clazz.equals(UNDEFINED.getClass())
|| /*after deserialization it is only object */clazz.equals(Object.class) || clazz.isArray();
}
public List> getBeanAttributes() {
//workaround for collection generic
Object attributes = getAttributes(false);
return (List>) attributes;
}
/**
* perhaps you may reference the given map in the next bean instance of same type.
*
* @return definition map
*/
protected Map> getAttributeDefinitions() {
if (attributeDefinitions == null) {
attributeDefinitions = new LinkedHashMap>();
}
return attributeDefinitions;
}
/**
* gets an attribute of this bean. if you defined a filter, only attributes defined in that filter are accessible.
* if you try to access a virtual beanvalue ({@link BeanValue#isVirtual()=true}) you don't have to use the real
* attribute name (this would be always {@link IValueAccess#ATTR_VALUE} but the description (
* {@link BeanValue#getDescription()}).
*
* @param name attribute name (on virtual attributes it is the description of the {@link BeanValue}).
* @return attribute
*/
@Override
public IAttributeDefinition getAttribute(String name) {
IAttributeDefinition definition = getAttributeDefinitions().get(name);
if (definition == null) {
if (!allDefinitionsCached) {
if (isVirtual()) {
throw ManagedException
.implementationError(
"The attribute "
+ name
+ " was not defined in this virtual bean!\nPlease define this attribute through addAttribute(...)",
name);
}
definition = createAttributeDefinition(name);
attributeDefinitions.put(name, definition);
} else {
throw new IllegalArgumentException("The attribute '" + name + "' is not defined in bean " + this);
}
}
return definition;
}
/**
* {@inheritDoc}
*/
@Override
public String[] getAttributeNames(boolean readAndWriteAccess) {
if (attributeFilter == null) {
if (isVirtual() && attributeDefinitions != null) {
attributeFilter = attributeDefinitions.keySet().toArray(new String[0]);
} else if (!isVirtual()) {
if (allDefinitionsCached) {
attributeFilter = attributeDefinitions.keySet().toArray(new String[0]);
} else {
attributeFilter = super.getAttributeNames(readAndWriteAccess);
}
} else {
attributeFilter = new String[0];
}
createNaturalSortedAttributeNames(attributeFilter);
}
return attributeFilter;
}
/**
* create performance enhancing cache
*/
private void createNaturalSortedAttributeNames(String[] attributeNames) {
naturalSortedAttributeNames = CollectionUtil.copyOfRange(attributeNames,
0,
attributeNames.length,
String[].class);
Arrays.sort(naturalSortedAttributeNames);
}
/**
* @delegates to {@link #addAttribute(String, IAttribute, String, IPresentable)}
*/
public AttributeDefinition addAttribute(IAttribute expression) {
return addAttribute(expression.getName(), expression, null, null);
}
/**
* creates a new attribute with value. internally, a new {@link ValueHolder} instance will be created to work on its
* {@link ValueHolder#getValue()} attribute. the value-expression will be added to work instead of the standard
* mechanism. see {@link AttributeDefinition#setExpression(String)}.
*
* @param name desired name of the attribute
* @param expression value expression to be used as attribute-value
* @param format (optional) regexp to constrain the value
* @param description (optional) description of this attribute. if null, the name will be used.
* @param presentation (optional) abstract presentation informations
* @return new added bean value
*/
public AttributeDefinition addAttribute(String name,
IAttribute expression,
String description,
IPresentable presentation) {
AttributeDefinition bv = new AttributeDefinition(expression);
bv.setBasicDef(-1, true, null, null, description != null ? description : name);
bv.setPresentation(presentation);
return (AttributeDefinition) addAttribute(bv.getName(), bv);
}
/**
* creates a new attribute with value. internally, a new {@link ValueHolder} instance will be created to work on its
* {@link ValueHolder#getValue()} attribute.
*
* @param name desired name of the attribute
* @param value current value
* @param pattern (optional) regexp to constrain the value
* @param description (optional) description of this attribute. if null, the name will be used.
* @param presentation (optional) abstract presentation informations
* @return new add bean value
*/
public AttributeDefinition addAttribute(String name,
A value,
Format format,
String description,
IPresentable presentation) {
IValueAccess v = new ValueHolder(value);
BeanValue bv = new BeanValue(v, new VAttribute(name));
bv.setBasicDef(-1, true, format, value, description != null ? description : name);
bv.setPresentation(presentation);
return (AttributeDefinition) addAttribute(name, bv);
}
/**
* addAttribute
*
* @param instance
* @param name
* @param length
* @param nullable
* @param pattern
* @param defaultValue
* @param description
* @param presentation
* @return
*/
public IAttributeDefinition> addAttribute(Object instance,
String name,
int length,
boolean nullable,
Format format,
Object defaultValue,
String description,
IPresentable presentation) {
BeanValue bv = BeanValue.getBeanValue(instance, name);
description = description != null ? description : name;
bv.setBasicDef(length, nullable, format, defaultValue, description);
bv.setPresentation(presentation);
return addAttribute(bv);
}
/**
* adds a new attribute to this definition
*
* @param newAttribute attribute
* @return the new created attribute
*/
public IAttributeDefinition addAttribute(IAttributeDefinition newAttribute) {
return addAttribute(newAttribute.getName(), newAttribute);
}
/**
* addAttribute
*
* @param name
* @param newAttribute
* @return
*/
protected IAttributeDefinition addAttribute(String name, IAttributeDefinition newAttribute) {
getAttributeDefinitions().put(
name/*newAttribute.getName() != null ? newAttribute.getName() : newAttribute.getDescription()*/,
newAttribute);
//if no filter was defined, it will be prefilled in getAttributeNames()
if (attributeFilter == null) {
attributeFilter = getAttributeNames();
} else {
attributeFilter = CollectionUtil.concatNew(new String[attributeFilter.length + 1],
attributeFilter,
new String[] { name });
}
allDefinitionsCached = false;
return newAttribute;
}
/**
* setAttributeDefinitions
*
* @param definitionMap attribute definitions. mainly to constrain and validate values.
*/
public void setAttrDef(String name,
int length,
boolean nullable,
Format format,
Object defaultValue,
String description) {
getAttribute(name).setBasicDef(length, nullable, format, defaultValue, description);
}
/**
* setNumberDef
*
* @param name attribute name
* @param scale attribute number scale
* @param precision attribute number precision
*/
public void setNumberDef(String name, int scale, int precision) {
getAttribute(name).setNumberDef(scale, precision);
}
/**
* isPersistable
*
* @return
*/
public boolean isPersistable() {
return BeanContainer.isInitialized() && BeanContainer.instance().isPersistable(clazz);
}
/**
* isInterface
*
* @return
*/
public boolean isInterface() {
return getDefiningClass(clazz).isInterface();
}
/**
* the bean is selectable, if it has at least one action to do on it - or it has a default-constructor or is
* persistable.
*
* @return true, if an action was defined
*/
public boolean isSelectable() {
return !isFinal()
&& (isMultiValue() || isInterface() || isCreatable() || isPersistable()
|| Map.Entry.class.isAssignableFrom(getClazz()) || !Util.isEmpty(actions));
}
/**
* isCreatable
*
* @return true, if bean is non-standard type having a public default constructor.
*/
public boolean isCreatable() {
Class t = getDefiningClass(clazz);
return !BeanUtil.isStandardType(t) && BeanClass.hasDefaultConstructor(t);
}
/**
* getBeanActions
*
* @return all methods (wrapped into actions) starting with 'action' and having no arguments.
*/
public Collection getActionsByClass() {
return getActionsByClass(clazz, null);
}
/**
* getBeanActions
*
* @param clazz class to analyze
* @param actions (optional) collection to be filled with actions
* @return all public methods (wrapped into actions) starting with 'action' and having no arguments.
*/
public static Collection getActionsByClass(Class> clazz,
Collection actions,
Object... parameters) {
final Method[] methods = getDefiningClass(clazz).getMethods();
if (actions == null) {
actions = new ArrayList();
}
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName().startsWith(MethodAction.ACTION_PREFIX)
|| methods[i].isAnnotationPresent(Action.class)) {
CommonAction
© 2015 - 2025 Weber Informatics LLC | Privacy Policy