All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.tsl2.nano.bean.def.Bean Maven / Gradle / Ivy

Go to download

TSL2 Framework Descriptor (currency-handling, generic formatter, descriptors for beans, collections, actions and values)

There is a newer version: 2.5.1
Show newest version
/*
 * File: $HeadURL$
 * Id  : $Id$
 * 
 * created by: Thomas Schneider
 * created on: Oct 24, 2010
 * 
 * Copyright: (c) Thomas Schneider 2010, all rights reserved
 */
package de.tsl2.nano.bean.def;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.text.Format;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;

import de.tsl2.nano.action.IAction;
import de.tsl2.nano.action.IConstraint;
import de.tsl2.nano.action.IStatus;
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.collection.Entry;
import de.tsl2.nano.collection.TimedReferenceMap;
import de.tsl2.nano.core.ENV;
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.log.LogFactory;
import de.tsl2.nano.core.messaging.IListener;
import de.tsl2.nano.core.util.CollectionUtil;
import de.tsl2.nano.core.util.MapUtil;
import de.tsl2.nano.core.util.StringUtil;
import de.tsl2.nano.core.util.Util;

/**
 * full bean access with extended attribute definitions, registering observers, attribute validations etc.! uses bean
 * reflection base classes like {@link BeanClass} and {@link BeanAttribute}. through the extension interface
 * {@link IAttributeDefinition}, the {@link BeanValue} works in the background for handling all defined bean values.
 * 

* it is possible to create virtual beans - means, you create a bean without base instance through {@link #Bean()} and * are able to add attributes to that bean through * {@link #addAttribute(Object, String, int, boolean, String, Object, String, IPresentable)}. this provides the * possibility to avoid creating new java classes to have the bean definitions. *

* you are able to define actions for the bean with {@link #addAction(IAction)}. *

* use:
* - {@link #Bean(Object)} - {@link #setAttributeFilter(String...)}
* - {@link #setAttrDef(String, int, boolean, String, Object, String)}
* - {@link #getAttribute(String)}
* - {@link #getValue(String)}
* - {@link #setValue(String, Object)}
* - {@link #check()}
* - {@link #observe(String, IListener)} *

* conveniences and utilities:
* - {@link #toValueMap()} - {@link #toValueMap(String, boolean, String...)} *

* example test code:
* *

 * TypeBean inst1 = new TypeBean();
 * TypeBean inst2 = new TypeBean();
 * inst1.setObject(inst2);
 * 
 * Bean b1 = new Bean(inst1);
 * Bean b2 = new Bean(inst2);
 * 
 * b1.setAttributeFilter("string", "bigDecimal");
 * b2.setAttributeFilter("primitiveChar", "immutableInteger");
 * 
 * b1.setAttrDef("string", 5, false, "[A-Z]+", null, null);
 * 
 * b2.connect("primitiveChar", b1.getAttribute("string"), new CommonAction<Object>() {
 *     public Object action() throws Exception {
 *         LOG.info("starting connection callback...");
 *         return null;
 *     }
 * });
 * 
 * b1.observe("string", new de.tsl2.nano.bean.IValueChangeListener() {
 *     public void handleChange(de.tsl2.nano.bean.ValueChangeEvent changeEvent) {
 *         LOG.info(changeEvent);
 *     }
 * });
 * 
 * b1.setValue("string", "TEST");
 * b1.check();
 * 
 * b1.setValue("string", "test");
 * try {
 *     b1.check();
 *     fail("check on " + b1.getValue("string") + " must fail!");
 * } catch (IllegalArgumentException ex) {
 *     //ok
 * }
 * b1.setValue("string", "xxxxxxxxxxxxxxxxxxxxxxx");
 * try {
 *     b1.check();
 *     fail("check on " + b1.getValue("string") + " must fail!");
 * } catch (IllegalArgumentException ex) {
 *     //ok
 * }
 * b2.setValue("primitiveChar", 'X');
 * b2.setValue("immutableInteger", 99);
 * LOG.info(b1.getAttribute("object").getRelation("immutableInteger").getValue());
 * 
* * @author Thomas Schneider * @version $Revision$ */ @SuppressWarnings({ "rawtypes", "unchecked" }) public class Bean extends BeanDefinition { /** serialVersionUID */ private static final long serialVersionUID = 1192383647173369697L; private static final Log LOG = LogFactory.getLog(Bean.class); /** java object instance to evaluate the attributes for - may be null on virtual beans */ protected T instance; /** string representation. see {@link #toString()} */ protected String asString; /** inner function to detach this bean from a beancollector */ private IAction detacher; transient protected boolean addSaveAction = true; /** used to registere/find extensions of BeanDefinition/Bean through ENV property */ public static final String BEANWRAPPER = "BeanWrapper"; /** * hold beans only for a short time. this will enhance performance on loading a bean. holding beans to long will * result in memory problems as the application may be used by many clients. */ static final TimedReferenceMap timedCache = new TimedReferenceMap(); /** * constructor to create a virtual bean - without an object instance. all attribute definitions must be done by * yourself. */ public Bean() { this((T) UNDEFINED); //no reflection has to be done to evaluate the instance attributes! allDefinitionsCached = true; name = "virtualbean-" + System.currentTimeMillis(); } /** * constructor * * @param instance java instance to reflect */ public Bean(T instance) { super((Class) instance.getClass()); this.instance = instance; } /** * @return Returns the instance. */ public T getInstance() { return instance; } /** * unique id for this bean. on persistable beans, it is defined by evaluating {@link #getIdAttribute()} on current * instance. * * @return unique bean id */ @Override public Object getId() { IAttribute idAttribute = getIdAttribute(); return idAttribute != null ? idAttribute.getValue(instance) : super.getId(); } /** * only to be used by framework for performance aspects. * * @param instance */ public Bean setInstance(T instance) { //TODO: why clazz can be simple Object.class instead of UNDEFINED? if (clazz.equals(UNDEFINED.getClass()) || clazz.equals(Object.class)) { this.clazz = (Class) instance.getClass(); } this.asString = null; this.instance = instance; replaceInstanceInAttributes(instance); if (valueExpression != null && !valueExpression.hasArguments) valueExpression = null; if (actions != null) { for (IAction a : actions) { if (a instanceof IConstructable) ((IConstructable) a).setInstance(instance); } } return this; } @Override public Collection getActions() { if (actions == null) { if (!isVirtual()) { if (instance != null) { actions = getActionsByClass(instance.getClass(), null, new Object[] { instance }); actions = new ArrayList(actions); Collections.sort((List) actions); } } } if (addSaveAction && !hasOkAction(actions) && isSelectable()) { addDefaultSaveAction(); } return super.getActions(); } private boolean hasOkAction(Collection actions) { if (actions != null) { for (IAction a : actions) { if (a.getActionMode() == IAction.MODE_DLG_OK) return true; } } return false; } @Override public IValueDefinition getAttribute(String name) { return (IValueDefinition) super.getAttribute(name); } /** * evaluate the value of the given bean attribute path * * @param bean starting instance * @param path full relation path, separated by '.' * @return attribute value or null */ public Object getValue(String... path) { if (isVirtual()) { return getAttribute(path[0]).getValue(); } else { return BeanClass.getValue(instance, path); } } /** * getValues * * @param attributeNames attribute names. if no names are given, all attributes will be used * @return list of values ordered by attributeNames */ public Collection getValues(String... attributeNames) { if (attributeNames.length == 0) { attributeNames = getAttributeNames(); } final ArrayList values = new ArrayList(attributeNames.length); for (int i = 0; i < attributeNames.length; i++) { // values.add(BeanAttribute.getBeanAttribute(clazz, attributeNames[i]).getValue(instance)); values.add(getValue(attributeNames[i])); } return values; } /** * setValue * * @param attributeName attribute name * @param value new value */ public void setValue(String attributeName, Object value) { getAttribute(attributeName).setValue(value); } /** * delegates to {@link BeanValue#setParsedValue(String)} */ public void setParsedValue(String attributeName, String value) { ((BeanValue) getAttribute(attributeName)).setParsedValue(value); } /** * searches for the given attribute name. if it exists and it is writable (having a public setter) and the current * value differs from the new given value, the method {@link BeanValue#setParsedValue(String)} will be called. * * @param attributeName attribute to change, if existing with setter * @param value new value to set * @return true, if attribute was changed to new value */ public boolean changeToParsedValue(String attributeName, String value) { BeanValue bv; if (hasAttribute(attributeName) && (bv = (BeanValue) getAttribute(attributeName)).hasWriteAccess()) { if (!bv.getValueText().equals(value)) { bv.setParsedValue(value); return true; } } return false; } /** * @see #getValueAsBean(String, boolean) */ public BeanDefinition getValueAsBean(String name) { return getValueAsBean(name, true); } /** * wraps the attribute value into a bean. the attribute has to be an entity. * * @param name attribute name * @return new bean holding the attributes value. */ public BeanDefinition getValueAsBean(String name, boolean cacheInstance) { IValueDefinition attribute = getAttribute(name); if (BeanUtil .isStandardType(attribute.getType()) /*!BeanContainer.instance().isPersistable(attribute.getType())*/) { throw new ManagedException("The attribute '" + name + "' is not a persistable bean"); } Serializable value = (Serializable) attribute.getValue(); if (value == null) { return null; } Bean bean = value instanceof Bean ? (Bean) value : getBean(value, cacheInstance); return bean; } /** * convenience to observe an attribute * * @param name attribute name * @param listener observer */ public void observe(String name, IListener listener) { getAttribute(name).changeHandler().addListener(listener); } public IAttributeDefinition addAttribute(String name, int length, boolean nullable, Format format, Object defaultValue, String description, IPresentable presentation) { if (instance.equals(UNDEFINED)) { throw new IllegalStateException( "this bean has no real instance (UNDEFINED). if you add bean-attributes, they must have own instances!"); } return addAttribute(instance, name, length, nullable, format, defaultValue, description, presentation); } /** * isValid * * @param messages filled warning and error messages * @return true, if current value is ok */ public boolean isValid(Map, String> messages) { return isValid(messages, false); } public boolean isValid(Map, String> messages, boolean refresh) { final List> attributes = getBeanValues(); boolean valid = true; IStatus status = IStatus.STATUS_OK; for (final BeanValue beanValue : attributes) { if ((refresh && !(status = beanValue.isValid(beanValue.getValue())).ok()) || (!refresh && !beanValue.getStatus().ok())) { if (messages != null) messages.put(beanValue, beanValue.getStatus().message()); if (refresh) beanValue.status = status; if (!beanValue.status.ok()) { valid = false; } } } return valid; } /** * checks all attribute values and throws an {@link IllegalArgumentException}, if any value is not valid */ public void check() { check(false); } public void check(boolean refresh) { final Map, String> msgMap = new LinkedHashMap, String>(); final boolean isValid = isValid(msgMap, refresh); if (!isValid) { throw new IllegalArgumentException(StringUtil.toString(msgMap, 0)); } if (crossChecker != null) { crossChecker.check(); //TODO: refactore incubation rule to be usable here... // if (rule != null) { // RulePool.get(rule).run(...); // } } } public void addCrossValueChecker(BeanValue checkBean, ArrayList mustHave, ArrayList mustNotHave) { crossChecker.add(checkBean, mustHave, mustNotHave); } /** * {@inheritDoc} */ @Override protected IAttributeDefinition createAttributeDefinition(String name) { BeanValue beanValue = BeanValue.getBeanValue(instance, name); beanValue.setRange(getPresentationHelper().getDefaultAllowedValues(beanValue)); return beanValue; } /** * getBeanValues * * @return available bean attribute values */ public List> getBeanValues() { //workaround for collection generic Object result = getAttributes(); return (List>) result; } /** *

* Attention: this implementation uses the {@link BeanValue} cache - means, that the new instance may resist inside * that cache. override this method to initialize your bean. * * @param type object type * @param constructorArgs object type constructor arguments * @return new bean */ public void newInstance(Object... constructorArgs) { instance = createInstance(constructorArgs); replaceInstanceInAttributes(instance); } /** * replaceInstanceInAttributes * * @param instance2 */ Bean replaceInstanceInAttributes(T instance2) { if (attributeDefinitions != null) { for (final IAttributeDefinition a : attributeDefinitions.values()) { //TODO: what to do with virtual values? if (a.isVirtual() && a.getAccessMethod() != null) { continue; } ((BeanValue) a).setInstance(instance2); } } return this; } /** * creates a new bean with new object instance * * @param type object type * @param constructorArgs object type constructor arguments * @return new bean */ public static Bean newBean(Class type, Object... constructorArgs) { return new Bean(BeanClass.createInstance(type, constructorArgs)); } @Override public int hashCode() { return isVirtual() ? super.hashCode() : 31 * super.hashCode() + instance.hashCode(); } /** * addDefaultSaveAction * * @return new created default save action for {@link #instance}. */ public IAction addDefaultSaveAction() { SecureAction saveAction = createSaveAction(instance); addAction(saveAction); return saveAction; } /** * creates a save action for the given bean. the action-id will be 'bean.getClass().save' - that will be used as * permission id, too. * * @param bean bean to save */ protected SecureAction createSaveAction(final Object bean) { final String actionId = BeanContainer.getActionId(bean.getClass(), false, "save"); return createSaveAction(bean, actionId); } /** * creates a save action for the given bean. the action-id will be 'bean.getClass().save' - that will be used as * permission id, too. * * @param bean bean to save * @param actionId action id (important for user permissions!) */ protected SecureAction createSaveAction(final Object bean, String actionId) { final String saveLabel = BeanContainer.isInitialized() && BeanContainer.instance().isPersistable(getDefiningClass(clazz)) && !CompositionFactory.contains(bean) ? Messages .getString("tsl2nano.save") : Messages.getString("tsl2nano.assign"); return new SaveAction(bean, actionId, saveLabel, saveLabel, IAction.MODE_DLG_OK); } /** * save * * @return */ public Object save() { return /*setInstance(*/save(instance)/*)*/; } /** * overwrite this method to define your own saving mechanism * * @param bean normally the presenters bean, but on an unpersistable presenter-bean you should give the right * persistable entity. * @return saved and refreshed bean (please assign the new bean to the presenters {@link BasePresenter#data}) to be * used as presenters bean (see {@link BasePresenter#data}). on any catched error, you should return * {@link IAction#CANCELED} to inform the framework to cancel following gui-actions. then f.e. a dialog * won't be closed. */ protected Object save(Object bean) { //on not-persistable beans, the presentation layer has to handle the save or assignment if (!BeanContainer.isInitialized() || !BeanContainer.instance().isPersistable(getDefiningClass(clazz))) { return bean; } // do the save - fill the old bean with the new id value Object newBean; try { if (CompositionFactory.markToPersist(bean)) { //refresh the bean! // result = BeanContainer.instance().getBeansByExample(instance).iterator().next(); newBean = bean; } else { newBean = BeanContainer.instance().save(bean); /* * after the save operation, the presenter can be used only after * a BeanContainer.resolveLayzRelation() and a reset()-call. * * the gui using this presenter should be closed or recreated! */ newBean = BeanContainer.instance().resolveLazyRelations(newBean); } } catch (final RuntimeException e) { if (BeanContainer.isConstraintError(e)) { throw new ManagedException("tsl2nano.impossible_create", new Object[] { /*Configuration.current() .getDefaultFormatter() .format(*/bean /*)*/ }); } else { throw e; } } finally { // detach(); } //refresh the old bean with the new id final BeanAttribute idAttribute = BeanContainer.getIdAttribute(bean); if (idAttribute != null) { idAttribute.setValue(bean, idAttribute.getValue(newBean)); } //if the saved object is the presenters bean - use the new refreshed bean if (newBean.getClass().equals(instance.getClass())) { instance = (T) newBean; } return newBean; } /** * fills a map with all bean-attribute-names and their values. * * @param properties will be ignored (is only inherited) * @return map filled with all attribute values */ @Override public Map toValueMap(Map properties) { return toValueMap(instance, true, false, false); } protected static Bean createBean(I instance, BeanDefinition beandef) { return createBean(instance, beandef, new Bean()); } /** * creates a bean through informations of a bean-definition * * @param * @param instance instance of bean * @param beandef bean description * @return new created bean holding given instance */ protected static Bean createBean(I instance, BeanDefinition beandef, Bean beanWrapper) { Bean bean = beanWrapper; copy(beandef, bean, "attributeFilter", "attributeDefinitions", "asString", "presentationHelper", "presentable", "actions"); bean.attributeFilter = beandef.attributeFilter != null ? CollectionUtil.copy(beandef.attributeFilter) : null; bean.attributeDefinitions = (LinkedHashMap>) Util.untyped(createValueDefinitions(beandef .getAttributeDefinitions())); if (beandef.presentable != null) bean.presentable = BeanUtil.copy(beandef.presentable); if (beandef.actions != null) { bean.actions = new ArrayList(beandef.actions.size()); for (IAction a : beandef.actions) { bean.actions.add(a instanceof Serializable && !a.getClass().isAnonymousClass() ? BeanUtil.copy(a) : a); } } //give the new bean the chance to create actions...only if null if (bean.actions != null && bean.actions.size() == 0) { bean.actions = null; } bean.setInstance(instance); injectIntoRuleCovers(bean, instance); if (bean.getPlugins() != null) { for (IConnector p : bean.getPlugins()) { p.connect(bean); } } return bean; } /** * creates new enhanced value definitions from given attribute definitions. the values instance will be * {@link BeanDefinition#UNDEFINED}. * * @param attributeDefinitions attributes to copy and enhance * @return new map holding value definitions */ protected static LinkedHashMap> createValueDefinitions( Map> attributeDefinitions) { LinkedHashMap> valueDefs = new LinkedHashMap>(attributeDefinitions.size()); try { for (IAttributeDefinition attr : attributeDefinitions.values()) { // if (!(attr instanceof IValueAccess)) {//--> standard attribute-definition //use any simple arguments - they will be overwritten on next line in copy(...) IValueAccess valueDef = new BeanValue();/*UNDEFINED, AttributeDefinition.UNDEFINEDMETHOD) { @Override protected void defineDefaults() { // don't set any defaults - overwrite members in the next step } };*/ valueDef = copy(attr, valueDef, "parent"); //Workaround for 'parent' field in BeanValue to avoid a ConcurrentModificationException in Android if (attr instanceof BeanValue) ((BeanValue) valueDef).setParent(((BeanValue) attr).getParent()); BeanValue.beanValueCache.add((BeanValue) valueDef); if (valueDef instanceof IPluggable) { Collection plugins = ((IPluggable) valueDef).getPlugins(); if (plugins != null) { for (IConnector p : plugins) { p.connect(valueDef); } } } valueDefs.put(attr.getName(), valueDef); // } else {//it is a specialized beanvalue like pathvalue or ruleattribute // valueDefs.put(attr.getName(), (IValueAccess) BeanUtil.clone(attr)); // } } return valueDefs; } catch (Exception e) { ManagedException.forward(e); return null; } } /** * creates a bean with given instance or name. if you give a name, a virtual bean will be created and returned. if * you give an instance, a bean definition will be searched and copied to a new created bean. * * @param bean instance type * @param instanceOrName an object or string (--> {@link #isVirtual()}) * @param keysAndValues key-value-pairs to be filled to the given instance * @return new created bean */ public static Bean getBean(I instanceOrName, Object... keysAndValues) { Bean bean = getBean(instanceOrName); Map map = MapUtil.asMap(keysAndValues); Set keySet = map.keySet(); for (String key : keySet) { bean.setValue(key, map.get(key)); } return bean; } /** * @see #getBean(Serializable, boolean) */ public static Bean getBean(I instanceOrName) { return getBean(instanceOrName, true/*!BeanUtil.isStandardType(instanceOrName)*/); } /** * creates a bean with given instance or name. if you give a name, a virtual bean will be created and returned. if * you give an instance, a bean definition will be searched and copied to a new created bean. * * @param bean instance type * @param instanceOrName an object or string (--> {@link #isVirtual()}) * @return new created bean */ public static Bean getBean(I instanceOrName, boolean cacheInstance) { Bean bean = timedCache.get(instanceOrName); if (bean != null) { return bean; } if (instanceOrName instanceof String) { BeanDefinition beandef = (BeanDefinition) getBeanDefinition((String) instanceOrName); // check, if beandef is for virtual type if (!beandef.getDeclaringClass().equals(UNDEFINED.getClass())) { //beandef = new BeanDefinition(); // this would overwrite all configurations??? beandef.setName(instanceOrName.toString()); } bean = createBean((I) UNDEFINED, beandef); } else if (instanceOrName.getClass().isArray()) { bean = createArrayBean(instanceOrName); } else if (Map.class.isAssignableFrom(instanceOrName.getClass())) { bean = createMapBean(instanceOrName); } else if (Entry.class.isAssignableFrom(instanceOrName.getClass())) { BeanDefinition beandef = getBeanDefinition((Class) BeanClass.getDefiningClass(instanceOrName.getClass())); bean = createBean(instanceOrName, beandef); Entry entry = (Entry) instanceOrName; if (entry.getValue() != null) { IConstraint c = bean.getAttribute("value").getConstraint(); c.setFormat(null); c.setType(BeanClass.getDefiningClass(entry.getValue().getClass())); } } else { Class type = instanceOrName.getClass(); BeanDefinition beandef = getBeanDefinition((Class) BeanClass.getDefiningClass(instanceOrName.getClass())); Class instanceBeanWrapper = ENV.get(type.getName() + BEANWRAPPER, null); if (instanceBeanWrapper != null) { bean = createBean(instanceOrName, beandef, (Bean)BeanClass.createInstance(instanceBeanWrapper, instanceOrName)); } else { bean = createBean(instanceOrName, beandef); } } if (cacheInstance && ENV.get("bean.use.cache", true)) { timedCache.put(instanceOrName, bean); } return bean; } @Override //not yet used on creation public void autoInit(String name) { super.autoInit(name); List> beanValues = getBeanAttributes(); for (IAttributeDefinition bv : beanValues) { bv.getPresentation(); bv.getColumnDefinition(); } } private static Bean createArrayBean(Object array) { int length = Array.getLength(array); Bean bean = new Bean(array); for (int i = 0; i < length; i++) { bean.addAttribute(new BeanValue(bean.instance, new ArrayValue(String.valueOf(i), i))); } return bean; } private static Bean createMapBean(Object mapInstance) { Map map = (Map) mapInstance; Bean bean = new Bean(map); Set keySet = map.keySet(); Object v; if (map.keySet() != null) {//on a proxy instance, keySet() may return null! for (Object k : keySet) { v = map.get(k); bean.addAttribute( new BeanValue(bean.instance, new MapValue(v != null ? v : k, (v != null ? BeanClass.getDefiningClass(v .getClass()) : null), map))); } } return bean; } /** * attaches the given detacher * * @param detacher */ public void attach(IAction detacher) { this.detacher = detacher; } /** * cleans all bean relevant caches (BeanClass, BeanValue, BeanDefinition, Bean). * * @return amount of cleared objects. */ public static int clearCache() { CompositionFactory.clearCache(); int cleared = BeanValue.clearCache(); cleared += BeanClass.clearCache(); cleared += BeanDefinition.clearCache(); if (timedCache != null) { cleared += timedCache.size(); LOG.info("clearing bean/value/class cache of " + cleared + " elements"); timedCache.clear(); } return cleared; } @Override public void onDeactivation(Map context) { super.onDeactivation(context); //if a new object was cancelled, it must be removed if (!isMultiValue()) { detach("remove"); } //remove temp values of listeners List> bvs = getBeanValues(); for (BeanValue bv : bvs) { bv.changeHandler().reset(); } } /** * runs detacher and sets detacher to null. if arguments equals 'remove' this bean will be removed from a parent * list. */ public boolean detach(Object... arguments) { timedCache.remove(this); if (detacher != null) { detacher.setParameter(arguments); detacher.run(); detacher = null; return true; } else { return false; } } @Override public void setName(String name) { super.setName(name); asString = null; } public void setAddSaveAction(boolean addSaveAction) { this.addSaveAction = addSaveAction; } @Override public > B onActivation(Map context) { //on new beans, we fill manyToOne relations if exactly one item is available if (!BeanContainer.isInitialized() || BeanContainer.instance().isTransient(instance) && ENV.get("bean.new.fill.relations.on.one.item", true)) { String[] names = getAttributeNames(); for (int i = 0; i < names.length; i++) { IValueDefinition attr = getAttribute(names[i]); if (attr.isRelation() && !attr.isMultiValue() && attr.getValue() == null) { if (BeanContainer.getCount(attr.getType()) == 1) { attr.setValue(BeanContainer.instance().getBeans(attr.getType(), 0, -1).iterator().next()); } } } } super.onActivation(context); return (B) this; } @Override public Bean refreshed() { if (isStale()) return getBean(instance); return this; } public String toStringDescription() { final Collection attributes = getAttributes(); final StringBuilder buf = new StringBuilder(attributes.size() * 15); buf.append(getName() + " {"); for (final IAttribute beanAttribute : attributes) { if (beanAttribute instanceof BeanValue) { buf.append(beanAttribute.toString()); } else { buf.append(beanAttribute.getName() + "=" + beanAttribute.getValue(instance) + "\n"); } } buf.append("}"); return buf.toString(); } @Override protected void finalize() throws Throwable { //don't call a getter to evaluate attributes - the attributes would be created then if (attributeDefinitions != null) { for (IAttributeDefinition bv : attributeDefinitions.values()) { if (bv instanceof BeanValue) BeanValue.beanValueCache.remove(bv); } } super.finalize(); } @Override public String toString() { if (asString == null) { asString = toString(instance); } return asString; } } /** * To be serializable, we had to extract a full class instead of using the shorter inline class. Only used at * {@link Bean#createSaveAction(Object, String)}. * * @author Thomas Schneider * @version $Revision$ */ @SuppressWarnings({ "unchecked" }) class SaveAction extends SecureAction implements Serializable, IConstructable { /** serialVersionUID */ private static final long serialVersionUID = -3009132079876910521L; /** instance to save - may differ from bean.instance! */ private T instance; protected SaveAction() {} public SaveAction(T instance, String id, String shortDescription, String longDescription, int actionMode) { super(id, shortDescription, longDescription, actionMode); this.instance = instance; } @Override public T action() throws Exception { return (T) Bean.getBean(instance).save(); } @Override public String getImagePath() { return "icons/save.png"; } @Override public T getInstance() { return instance; } @Override public void setInstance(T instance) { this.instance = instance; } }