de.tsl2.nano.bean.def.BeanDefinition Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tsl2.nano.descriptor Show documentation
Show all versions of tsl2.nano.descriptor Show documentation
TSL2 Framework Descriptor (currency-handling, generic formatter, descriptors for beans, collections, actions and values)
/*
* 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