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

de.tsl2.nano.bean.def.AttributeDefinition 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 13, 2012
 * 
 * Copyright: (c) Thomas Schneider 2012, all rights reserved
 */
package de.tsl2.nano.bean.def;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.Format;
import java.text.ParseException;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;
import java.util.Map;
import java.util.UUID;

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.Element;
import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.core.Commit;
import org.simpleframework.xml.core.Persist;

import de.tsl2.nano.action.IActivable;
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.IAttributeDef;
import de.tsl2.nano.bean.IConnector;
import de.tsl2.nano.bean.IRuleCover;
import de.tsl2.nano.bean.IValueAccess;
import de.tsl2.nano.bean.ValueHolder;
import de.tsl2.nano.core.ENV;
import de.tsl2.nano.core.ManagedException;
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.cls.UnboundAccessor;
import de.tsl2.nano.core.log.LogFactory;
import de.tsl2.nano.core.messaging.EventController;
import de.tsl2.nano.core.messaging.IListener;
import de.tsl2.nano.core.secure.ISecure;
import de.tsl2.nano.core.util.ByteUtil;
import de.tsl2.nano.core.util.DelegationHandler;
import de.tsl2.nano.core.util.FormatUtil;
import de.tsl2.nano.core.util.MapUtil;
import de.tsl2.nano.core.util.NumberUtil;
import de.tsl2.nano.core.util.StringUtil;
import de.tsl2.nano.core.util.Util;
import de.tsl2.nano.format.RegExpFormat;

/**
 * 
 * @author Thomas Schneider
 * @version $Revision$
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
@Default(value = DefaultType.FIELD, required = false)
public class AttributeDefinition implements IAttributeDefinition {

    /** serialVersionUID */
    private static final long serialVersionUID = 1403875731423120506L;

    @Element(name = "declaring")
    protected IAttribute attribute;
    protected EventController eventController;
    @Element(type = Constraint.class, required = false)
    protected IConstraint constraint;
    @Attribute(required = false)
    private boolean id;
    @Attribute(required = false)
    private boolean unique;
    @Element(required = false)
    private Class temporalType;
    @Element(required = false)
    protected String description;
    protected transient IStatus status;
    @Element(type = Presentable.class, required = false)
    private IPresentable presentable;
    @Element(type = ValueColumn.class, required = false)
    private IPresentableColumn columnDefinition;
    @Attribute(required = false)
    private boolean doValidation = true;
    /** see {@link #composition()} */
    @Attribute(required = false)
    private boolean composition;
    /** see {@link #cascading()} */
    @Attribute(required = false)
    private boolean cascading;
    /** see {@link #generatedValue()} */
    @Attribute(required = false)
    private boolean generatedValue;

    /** optional encryption */
    @Element(required = false)
    private ISecure secure;

    /**
     * optional plugins.
     */
    @ElementList(inline = true, entry = "plugin", required = false)
    protected Collection plugins;

    private static final Log LOG = LogFactory.getLog(AttributeDefinition.class);

    /** internal definition to create a temporarily new beanvalue instance. */
    static final Method UNDEFINEDMETHOD = UNDEFINEDMETHOD();

    /**
     * constructor to be serializable
     * 
     * @throws SecurityException
     * @throws NoSuchMethodException
     */
    protected AttributeDefinition() {
        super();
        status = Status.STATUS_OK;
    }

    /**
     * constructor
     * 
     * @param attribute
     */
    public AttributeDefinition(IAttribute attribute) {
        super();
        this.attribute = attribute;
        if (attribute.getAccessMethod() != null) {
            defineDefaults();
        }
    }

    public AttributeDefinition(String name, IConstraint constraint) {
        this(new VAttribute(name));
        this.constraint = constraint;
    }

    protected AttributeDefinition(Method readAccessMethod) {
        super();
        attribute = new BeanAttribute(readAccessMethod);
        defineDefaults();
    }

    /**
     * default readaccessmethod if default constructor was invoked
     */
    private static final Method UNDEFINEDMETHOD() {
        try {
            //get yourself
            return AttributeDefinition.class.getDeclaredMethod("UNDEFINEDMETHOD", new Class[0]);
        } catch (Exception e) {
            ManagedException.forward(e);
            return null;
        }
    }

    /**
     * sets default properties through the {@link BeanContainer}, if available. These defaults may be read from
     * jpa-annotations.
     */
    protected void defineDefaults() {
        defineFromAnnotations();
        if (BeanContainer.isInitialized() && BeanContainer.instance().isPersistable(getDeclaringClass())) {
            IAttributeDef def = BeanContainer.instance().getAttributeDef(getDeclaringClass(), getName());
            if (def != null) {
                LOG.debug("setting defaults from annotations for attribute: " + getName());
                setId(def.id());
                setUnique(def.unique());
                setBasicDef(def.length(), def.nullable(), null, null, null);
                getConstraint().setNumberDef(def.scale(), def.precision());
                temporalType = def.temporalType();
                composition = def.composition();
                cascading = def.cascading();
                generatedValue = def.generatedValue();
            }
        }
        if (status == null)
            initDeserialization();
    }

    private void defineFromAnnotations() {
        Method m = getAccessMethod();
        if (m != null) {
            if (constraint == null && m.isAnnotationPresent(de.tsl2.nano.bean.annotation.Constraint.class)) {
                de.tsl2.nano.bean.annotation.Constraint c =
                    m.getAnnotation(de.tsl2.nano.bean.annotation.Constraint.class);
                constraint = new Constraint(c.type(), c.allowed());
                constraint.setBasicDef(c.length(), c.nullable(),
                    new RegExpFormat(c.pattern(), 255), (T) Util.nonEmpty(c.defaultValue()));
            }
            if (presentable == null && m.isAnnotationPresent(de.tsl2.nano.bean.annotation.Presentable.class)) {
                de.tsl2.nano.bean.annotation.Presentable p =
                    m.getAnnotation(de.tsl2.nano.bean.annotation.Presentable.class);
                presentable = new Presentable(this);
                presentable.setPresentation(p.label(), p.type(), p.style(), p.enabled() ? IActivable.ACTIVE : IActivable.INACTIVE, p.visible()
                    , (Serializable)MapUtil.asMap(p.layout()), (Serializable)MapUtil.asMap(p.layoutConstraints()), p.description());
                presentable.setIcon(p.icon());
            }
            if (columnDefinition == null && m.isAnnotationPresent(de.tsl2.nano.bean.annotation.Column.class)) {
                de.tsl2.nano.bean.annotation.Column c =
                    m.getAnnotation(de.tsl2.nano.bean.annotation.Column.class);
                columnDefinition = new ValueColumn(this);
                ValueColumn vc = (ValueColumn) columnDefinition;
                vc.name = c.name();
                vc.format = new RegExpFormat(c.pattern(), 255);
                vc.columnIndex = c.index();
                vc.width = c.width();
                vc.sortIndex = c.sortIndex();
                vc.isSortUpDirection = c.sortUp();
//                vc.minsearch = c.min();
//                vc.maxsearch = c.max();
            }
        }
    }

    @Persist
    private void initSerialization() {
        //disconnect from beandefinition to be serializable
        if (plugins != null) {
            for (IConnector p : plugins) {
                LOG.info("disconnecting plugin " + p + " from " + this);
                p.disconnect(this);
            }
        }
    }

    @Commit
    private void initDeserialization() {
        status = IStatus.STATUS_OK;
        if (getColumnDefinition() != null && getColumnDefinition() instanceof ValueColumn) {
            ((ValueColumn) getColumnDefinition()).attributeDefinition = this;
        }
        //injectAttributeOnChangeListeners() will be called from BeanDefinition
        injectIntoPlugins(this);
    }

    /**
     * injectPlugins
     */
    protected void injectIntoPlugins(IAttributeDefinition attr) {
        //connect optional plugins
        if (plugins != null) {
            for (IConnector p : plugins) {
                LOG.info("connecting plugin " + p + " to " + attr);
                p.connect(attr);
            }
        }
    }

    /**
     * injectRuleCover
     * 
     * @param attr new attribute definition to connect to rule cover instance
     */
    protected void injectIntoRuleCover(IValueDefinition attr) {
        //connect optional rule-covers (use accessor instead of BeanDefinition to avoid stackoverflow
        injectIntoRuleCover(new PrivateAccessor(attr), attr.getInstance());
    }

    /**
     * inject the given attribute as context - walks recursive to the member tree of acc
     * 
     * @param acc direct/indirect member of attr in member tree
     * @param instance to be set as context object in all RuleCover Proxies.
     */
    protected static  void injectIntoRuleCover(UnboundAccessor acc, Object instance) {
        //connect optional rule-covers (use accessor instead of BeanDefinition to avoid stackoverflow
        Map members = acc.members();
        InvocationHandler handler;
        Object item;
        for (Object k : members.keySet()) {
            item = members.get(k);
            if (item != null) {
                //first inject the child tree - be careful, don't produce a stackoverflow
                if (item != instance && Util.isFrameworkClass(item.getClass()) && !item.getClass().isAnonymousClass()
                    && !(item instanceof IAttribute) && !(item instanceof BeanDefinition))
                    injectIntoRuleCover(new PrivateAccessor(item), instance);
                //now the own direct members
                if (Proxy.isProxyClass(item.getClass())) {
                    handler = Proxy.getInvocationHandler(item);
                    //create proxy for each bean instance
                    if (handler instanceof DelegationHandler && handler instanceof IRuleCover) {
                        // compare instances: if attr is a delegation-handler we must ignore its delegate!
                        if (item == instance) {
                            LOG.warn/*throw new IllegalStateException*/("the given instance " + instance
                                + " seems to be a rulecover itself!");
                            continue;
                        }
                        handler = (InvocationHandler) ((DelegationHandler) handler).clone();
                        item = DelegationHandler.createProxy((DelegationHandler) handler);
                        acc.set((String) k, item);
                        new UnboundAccessor(handler).call("setContext", null, new Class[] { Serializable.class },
                            instance);
                    }
                }
            }
        }
    }

    /**
     * uses the information of {@link #attributeID} to inject the real {@link AttributeDefinition} into registered
     * change listeners
     * 
     * @throws CloneNotSupportedException
     */
    void injectAttributeOnChangeListeners(BeanDefinition beandef) {
        //provide dependency listeners their attribute-definition
        if (hasListeners()) {
            Collection listener = changeHandler().getListeners(null);
            boolean isBean = beandef instanceof Bean;
            for (IListener l : listener) {
                if (l instanceof AbstractDependencyListener) {
                    AbstractDependencyListener dl = (AbstractDependencyListener) l;
                    if (isBean) {//create a specific listener instance for the given bean!
                        dl = (AbstractDependencyListener) dl.clone();
                        Class eventType = changeHandler().getEventType(l);
                        changeHandler().removeListener(l);
                        changeHandler().addListener(dl, eventType);
                    }
                    if (dl.attributeID != null) {
                        String name = StringUtil.substring(dl.attributeID, ".", null);
                        dl.setAttribute((AttributeDefinition) beandef.getAttribute(name));
                    }
                }
            }
        }
    }

    @Override
    public final IConstraint getConstraint() {
        if (constraint == null) {
            constraint = new Constraint(BeanClass.getDefiningClass(attribute.getType()));
        }
        return constraint;
    }

    /**
     * setBasicDef
     * 
     * @param length {@link #length()}
     * @param nullable {@link #nullable()}
     * @param format {@link #getPattern()}
     * @param defaultValue {@link #getDefault()}
     * @param description {@link #getDescription()}
     */
    @Override
    public IAttributeDefinition setBasicDef(int length,
            boolean nullable,
            Format format,
            T defaultValue,
            String description) {
        try {
            getConstraint().setBasicDef(length, nullable, format, defaultValue);
        } catch (Exception e) {
            if (doValidation) {
                ManagedException.forward(e);
            }
        }
        this.description = description;
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public T getValue(Object beanInstance) {
        try {
            //using the default may result in problems on checking the value (e.g. in value-expression or isValue())
            T result = /*beanInstance == null && isVirtualAccess() ? getDefault() : */attribute.getValue(beanInstance);
            if (result != null && secure != null)
                result = (T) secure.decrypt((String) result);
            return result;
        } catch (Exception ex) {
            LOG.error("error evaluating value for attribute '" + getName() + "'", ex);
            status = new Status(ex);
            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IAttributeDefinition setId(boolean isId) {
        this.id = isId;
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IAttributeDefinition setUnique(boolean isUnique) {
        this.unique = isUnique;
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int length() {
        return getConstraint().getLength();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int scale() {
        return getConstraint().getScale();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int precision() {
        return getConstraint().getPrecision();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean nullable() {
        return getConstraint().isNullable();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean id() {
        return id;
    }

    @Override
    public boolean unique() {
        return unique;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Class temporalType() {
        return temporalType;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Format getFormat() {
        if (getConstraint().getFormat() == null) {
            Class type = getType();
            if (Collection.class.isAssignableFrom(type)) {
                getConstraint().setFormat(new CollectionExpressionTypeFormat(getGenericType(0)));
            } else if (Map.class.isAssignableFrom(type)) {
                getConstraint().setFormat(new MapExpressionFormat(getGenericType(1)));
            } else if (type.isEnum()) {
                getConstraint().setFormat(FormatUtil.getDefaultFormat(type, true));
            } else if (BeanUtil.isStandardType(type) && !ByteUtil.isByteStream(type)) {
                getConstraint().setFormat(ENV.get(BeanPresentationHelper.class).getDefaultRegExpFormat(this));
            } else {
                getConstraint().setFormat(new ValueExpressionTypeFormat(type));
            }
        }
        return getConstraint().getFormat();
    }

    @Override
    public ValueExpression getValueExpression() {
        Format f = getFormat();
        return f instanceof ValueExpressionFormat ? ((ValueExpressionFormat) f).getValueExpression() : null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Class getType() {
        return attribute.getType();
    }

    @Override
    public Method getAccessMethod() {
        return attribute.getAccessMethod();
    }

    protected Class getGenericType(int pos) {
        return (Class) (getAccessMethod() != null ? BeanAttribute.getGenericType(getAccessMethod(), pos)
            : Object.class);
    }

    /**
     * isMultiValue
     * 
     * @return true, if the value type is a collection
     */
    @Override
    public boolean isMultiValue() {
        Class type = getType();
        return Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type);
    }

    /**
     * see {@link BeanClass#getActions()} and {@link BeanDefinition#isSelectable()}
     * 
     * @param beanValue attribute to evaluate
     * @return true, if bean type is selectable
     */
    public boolean isSelectable() {
        Class type = getType();
        return isMultiValue()
            || (!BeanUtil.isStandardType(type) && BeanDefinition.getBeanDefinition(type).isSelectable());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IStatus isValid(T value) {
        return getConstraint().checkStatus(getId(), value);
    }

    /**
     * getParsedValue. throws a runtime exception on parsing error.
     * 
     * @param source text to parse
     * @return parsed value
     */
    public T getParsedValue(String source) {
        T value = null;
        if (getFormat() != null) {
            try {
                if (Util.isEmpty(source) && nullable())
                    return null;
                //the parser will decide, how to handle empty/null values
                value = (T) getFormat().parseObject(source);
            } catch (ParseException e) {
                ManagedException.forward(e);
            }
        } else if (String.class.isAssignableFrom(getType())) {
            value = (T) source;
        } else {
            throw new ManagedException("no format/parser available for field " + getName());
        }
        return value;
    }

    /**
     * hasStatusError
     * 
     * @return true, if has status.error != null
     */
    public boolean hasStatusError() {
        return status != null && status.error() != null;
    }

    /**
     * {@inheritDoc}
     */
    public void check() {
        if (hasStatusError()) {
            throw new RuntimeException(status.error());
        }
    }

    /**
     * {@inheritDoc}
     */
    public T getDefault() {
        IConstraint c = getConstraint();
        if (c.getDefault() == null && getAccessMethod() != null && !c.isNullable()
            && !BeanPresentationHelper.isGeneratedValue(this)) {
            Object genType = getAccessMethod().getGenericReturnType();
            if (genType instanceof Class) {
                Class gtype = (Class) genType;
                if (BeanUtil.isStandardType(gtype)) {
                    if (BeanClass.hasDefaultConstructor(gtype)) {
                        getConstraint().setDefault(BeanClass.createInstance(gtype));
                    }
                }
            }
            if (c.getDefault() == null) {
                if (NumberUtil.isNumber(getType())) {
                    getConstraint().setDefault((T) NumberUtil.getDefaultInstance((Class) getType()));
                } else if (ENV.isTestMode()) {
                    /*
                     * to create new entities without user input, these fields are filled on test mode
                     */
                    if (CharSequence.class.isAssignableFrom(getType())) {
                        c.setDefault((T) ("Y" + UUID.randomUUID().toString().substring(0, c.getLength() - 1)));
                    } else if (BeanContainer.instance().isPersistable(getType())) {
                        Collection beans = BeanContainer.instance().getBeans(getType(), 0, 1);
                        if (isMultiValue()) {
                            c.setDefault((T) beans);
                        } else if (beans.size() > 0) {
                            c.setDefault(beans.iterator().next());
                        }
                    }
                }
            }
        }
        return getConstraint().getDefault();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getDescription() {
        if (description == null && getName() != null) {
            if (getPresentation() != null) {
                description = getPresentation().getDescription();
            } else {
                description = StringUtil.toFirstUpper(getName());
            }
        }
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IAttributeDefinition setNumberDef(int scale, int precision) {
        getConstraint().setNumberDef(scale, precision);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IAttributeDefinition setRange(Comparable min, Comparable max) {
        getConstraint().setRange(min, max);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IAttributeDefinition setRange(Collection allowedValues) {
        getConstraint().setRange(allowedValues);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IAttributeDefinition setFormat(Format format) {
        getConstraint().setFormat(format);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IAttributeDefinition setLength(int length) {
        getConstraint().setLength(length);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IAttributeDefinition setScale(int scale) {
        getConstraint().setScale(scale);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IAttributeDefinition setPrecision(int precision) {
        getConstraint().setPrecision(precision);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IAttributeDefinition setNullable(boolean nullable) {
        getConstraint().setNullable(nullable);
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IStatus getStatus() {
        return status;
    }

    public void setStatus(IStatus status) {
        this.status = status;
    }
    
    /** see IVirtualDefinition#isRelation() */
    @Override
    public boolean isRelation() {
        return BeanContainer.isConnected()
            && BeanContainer.instance().isPersistable(getType());
    }

    /**
     * if the bean instance is of type {@link ValueHolder}, the attribute is virtual - no special bean-attribute is
     * available, the attribute name is always {@link ValueHolder#getValue()} .
     * 
     * @return true, if the declaring class is of type {@link IValueAccess}.
     */
    @Override
    public boolean isVirtual() {
        return attribute.isVirtual();
    }

    protected boolean isVirtualAccess() {
        return isVirtual() && getAccessMethod() != null;
    }

    /**
     * getPresentation
     * 
     * @return
     */
    @Override
    public IPresentable getPresentation() {
        //TODO: create presentation helper through MultipleInheritanceProxy
        if (presentable == null) {
            presentable = ENV.get(BeanPresentationHelper.class).createPresentable(this);
        }
        return presentable;
    }

    /**
     * setPresentation
     * 
     * @param label
     * @param type
     * @param style
     * @param enabler
     * @param visible
     * @param layout
     * @param layoutConstraints
     * @param description
     * @return
     */
    public IPresentable setPresentation(final String label,
            final int type,
            final int style,
            final IActivable enabler,
            final boolean visible,
            final Map layout,
            final Map layoutConstraints,
            final String description) {
        presentable = ENV.get(BeanPresentationHelper.class).createPresentable();
        presentable.setPresentation(label, type, style, enabler, visible, (Serializable) layout,
            (Serializable) layoutConstraints, description);
        return presentable;
    }

    /**
     * setPresentation
     * 
     * @param presentable
     */
    public void setPresentation(IPresentable presentable) {
        this.presentable = presentable;
    }

    /**
     * perhaps that definition was build yet. use BeanCollector.getColumnDefinition() to get the column - on first time
     * they will be created.
     * 
     * @return Returns the columnDefinition.
     */
    @Override
    public IPresentableColumn getColumnDefinition() {
        return columnDefinition;
    }

    /**
     * @param columnDefinition The columnDefinition to set.
     */
    public void setColumnDefinition(IPresentableColumn columnDefinition) {
        this.columnDefinition = columnDefinition;
    }

    /**
     * setColumnDefinition
     * 
     * @param index
     * @param sortIndex
     * @param sortUpDirection
     * @param width
     */
    @Override
    public void setColumnDefinition(int index, int sortIndex, boolean sortUpDirection, int width) {
        this.columnDefinition = new ValueColumn(this, index, sortIndex, sortUpDirection, width);
        this.columnDefinition.setPresentable(getPresentation());
    }

    /**
     * @return Returns the doValidation.
     */
    public boolean isDoValidation() {
        return doValidation;
    }

    /**
     * @param doValidation The doValidation to set.
     */
    public void setDoValidation(boolean doValidation) {
        this.doValidation = doValidation;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean composition() {
        return composition;
    }

    @Override
    public boolean cascading() {
        return cascading;
    }

    @Override
    public boolean generatedValue() {
        return generatedValue;
    }

    @Override
    public void setAsRelation(String relationChain) {
        new PrivateAccessor>(this).set("name", relationChain);
    }

    /**
     * @return Returns the plugins.
     */
    @Override
    public Collection getPlugins() {
        return plugins;
    }

    /**
     * @param plugin The plugin to add.
     */
    @Override
    public void addPlugin(IConnector plugin) {
        if (plugins == null) {
            plugins = new LinkedList();
        }
        LOG.info("connecting plugin " + plugin + " to " + this);
        plugin.connect(this);
        plugins.add(plugin);
    }

    /**
     * removePlugin
     * 
     * @param plugin to remove
     * @return true, if plugin was removed
     */
    @Override
    public boolean removePlugin(IConnector plugin) {
        if (plugins == null) {
            LOG.warn("plugin " + plugin + " can't be removed. no plugins available yet!");
            return false;
        }
        LOG.info("disconnecting plugin " + plugin + " from " + this);
        plugin.disconnect(this);
        return plugins.remove(plugin);
    }

///////////////////////////////////////////////////////////////////////////////
// Delegators to IAttribute
///////////////////////////////////////////////////////////////////////////////

    /**
     * {@inheritDoc}
     */
    @Override
    public Class getDeclaringClass() {
        return attribute.getDeclaringClass();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getName() {
        return attribute.getName();
    }

    @Override
    public void setName(String name) {
        if (!isVirtual()) {
            throw new IllegalStateException("name cannot be changed on non-virtual (=fixed) attributes!");
        }
        attribute.setName(name);
    }

    public String getPath() {
        return getDeclaringClass().getName() + "." + getName();
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    public void setValue(Object instance, T value) {
        if (secure != null)
            value = (T) secure.encrypt((String) value);
        attribute.setValue(instance, value);
    }

    /**
     * @return Returns the secure.
     */
    public ISecure getSecure() {
        return secure;
    }

    /**
     * @param secure The secure to set.
     */
    public void setSecure(ISecure secure) {
        if (String.class.isAssignableFrom(getType()))
            throw new IllegalStateException("encrypted fields must be of type String.class");
        this.secure = secure;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getId() {
        return attribute.getId();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean hasWriteAccess() {
        return attribute.hasWriteAccess();
    }

    /**
     * looks for registered change handlers without creating an {@link EventController} instance.
     * 
     * @return
     */
    public boolean hasListeners() {
        return eventController != null && eventController.hasListeners();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public EventController changeHandler() {
        if (eventController == null) {
            eventController = new EventController();
        }
        return eventController;
    }

    @Override
    public boolean equals(Object obj) {
        return hashCode() == obj.hashCode();
    }

    @Override
    public int hashCode() {
        return attribute != null ? attribute.hashCode() : super.hashCode();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int compareTo(IAttribute o) {
        return attribute.compareTo(o);
    }

    @Override
    public String toString() {
        return attribute != null ? attribute.toString() : super.toString();
    }

    public String toDebugString() {
        return Util.toString(getClass(), "declaringClass: " + getType(), "temporal-type: " + temporalType, "name: "
            + getName(),
            "id: " + id, "unique: " + unique, "cascading: " + cascading, "composition: " + composition, "\nattribute: "
                + attribute,
            "\nstatus: "
                + status,
            "\nconstraints: "
                + constraint,
            "\npresentable: " + presentable);
    }
}