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

de.tsl2.nano.bean.BeanContainer 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: Feb 4, 2010
 * 
 * Copyright: (c) Thomas Schneider 2010, all rights reserved
 */
package de.tsl2.nano.bean;

import java.io.Serializable;
import java.util.Collection;
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 de.tsl2.nano.action.CommonAction;
import de.tsl2.nano.action.IAction;
import de.tsl2.nano.bean.def.Bean;
import de.tsl2.nano.bean.def.BeanDefinition;
import de.tsl2.nano.bean.def.BeanValue;
import de.tsl2.nano.bean.def.IAttributeDefinition;
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.util.DateUtil;
import de.tsl2.nano.core.util.ListSet;
import de.tsl2.nano.core.util.NumberUtil;
import de.tsl2.nano.core.util.StringUtil;

/**
 * class to be used as simple one instance bean container. initialize it with your service actions (through calls to
 * specific service factories). it is a delegating class to avoid depending to a service factory and its services.
 * 
 * @author Thomas Schneider
 * @version $Revision$
 */
@SuppressWarnings({ "serial", "rawtypes", "unchecked" })
public class BeanContainer implements IBeanContainer {
    /** we provide a thread invariant and a thread variant singelton instance - this is not secure but practicable. */
    private static BeanContainer self = null;
    private static ThreadLocal selfThread = new ThreadLocal();

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

    /** on initializing with {@link #initEmtpyServiceActions()} it is true */
    private boolean emptyServices = false;

    protected IAction> idFinderAction = null;
    protected IAction> typeFinderAction = null;
    protected IAction> exampleFinderAction = null;
    protected IAction> betweenFinderAction = null;
    protected IAction> queryAction = null;
    protected IAction> queryMapAction = null;
    protected IAction lazyrelationInstantiateAction = null;
    protected IAction saveAction = null;
    protected IAction deleteAction = null;
    protected IAction attrdefAction = null;
    protected IAction permissionAction = null;
    protected IAction persistableAction = null;
    protected IAction executeAction = null;

    private BeanContainer() {
        super();
    }

    /**
     * you must have called {@link #initServiceActions(IAction, IAction, IAction)} before you can use the instance.
     * 
     * @return singelton instance
     */
    public static final BeanContainer instance() {
        assert self != null : "beancontainer not initialized! call initServiceActions(...) before!";
        return self();
    }
    
    private static BeanContainer self() {
        return selfThread.get() != null ? selfThread.get() : self;
    }

    /**
     * isInitialized
     * 
     * @return true, if {@link #initEmtpyServiceActions()} or
     *         {@link #initServiceActions(IAction, IAction, IAction, IAction, IAction, IAction, IAction, IAction, IAction, IAction)}
     *         was called before.
     */
    public static final boolean isInitialized() {
        return self() != null;
    }

    /**
     * call this method only on initializing your new application.
     * 
     * @param relationFinder action to find beans through a service
     * @param lazyRelationResolver to fetch all lazy relations
     * @param saveAction action to save bean through service
     * @param deleteAction action to delete bean through service
     * @param exampleFinder action to find beans through a service
     * @param betweenFinder action to find beans through a service
     * @param attrdefAction action to evaluate attribute definitions
     * @param permissionAction action to evaluate the permission (through roles)
     * @param persistableAction asks, if the given bean is persistable
     */
    public static final void initServiceActions(IAction> idFinder,
            IAction> relationFinder,
            IAction lazyRelationResolver,
            IAction saveAction,
            IAction deleteAction,
            IAction> exampleFinder,
            IAction> betweenFinder,
            IAction> queryFinder,
            IAction> queryMapFinder,
            IAction attrdefAction,
            IAction permissionAction,
            IAction persistableAction,
            IAction executeAction) {
        self = new BeanContainer();
        selfThread.set(self);
        
        self.idFinderAction = idFinder;
        self.typeFinderAction = relationFinder;
        self.lazyrelationInstantiateAction = lazyRelationResolver;
        self.saveAction = saveAction;
        self.deleteAction = deleteAction;
        self.exampleFinderAction = exampleFinder;
        self.betweenFinderAction = betweenFinder;
        self.queryAction = queryFinder;
        self.queryMapAction = queryMapFinder;
        self.attrdefAction = attrdefAction;
        self.permissionAction = permissionAction;
        self.persistableAction = persistableAction;
        self.executeAction = executeAction;
    }

    /**
     * will call {@link #initServiceActions(IAction, IAction, IAction)} with simple actions, doing nothing. useful for
     * testing.
     */
    public static final void initEmtpyServiceActions() {
        final Collection EMPTY_LIST = new ListSet();
        final IAction idFinder = new CommonAction("empty.service.idFinder") {
            @Override
            public Object action() {
                return null;
            }
        };
        final IAction> relationFinder = new CommonAction>("empty.service.relationFinder") {
            @Override
            public Collection action() {
                LOG.debug("calling empty-services relationFinder");
                return EMPTY_LIST;
            }
        };
        final IAction> exampleFinder = new CommonAction>("empty.service.exampleFinder") {
            @Override
            public Collection action() {
                LOG.debug("calling empty-services exampleFinder");
                return EMPTY_LIST;
            }
        };
        final IAction> betweenFinder = new CommonAction>("empty.service.betweenFinder") {
            @Override
            public Collection action() {
                LOG.debug("calling empty-services betweenFinder");
                return EMPTY_LIST;
            }
        };
        final IAction> queryFinder = new CommonAction>("empty.service.queryFinder") {
            @Override
            public Collection action() {
                LOG.debug("calling empty-services queryFinder");
                return EMPTY_LIST;
            }
        };
        final IAction> queryMapFinder = new CommonAction>("empty.service.queryMapFinder") {
            @Override
            public Collection action() {
                LOG.debug("calling empty-services queryMapFinder");
                return EMPTY_LIST;
            }
        };
        final IAction lazyRelationResolver = new CommonAction("empty.service.lazyRelationResolver") {
            @Override
            public Object action() {
                //do nothing, return the instance itself
                return getParameter()[0];
            }
        };
        final IAction saveAction = new CommonAction("empty.service.saveAction") {
            @Override
            public Object action() {
                LOG.debug("calling empty-services saveAction");
                //do nothing, return the instance itself
                return getParameter()[0];
            }
        };
        final IAction deleteAction = new CommonAction("empty.service.deleteAction") {
            @Override
            public Object action() {
                LOG.debug("calling empty-services deleteAction");
                //do nothing
                return null;
            }
        };
        final IAction attrAction = new CommonAction("empty.service.attrAction") {
            @Override
            public IAttributeDef action() {
                //do nothing
                return null;
            }
        };
        final IAction permissionAction = new CommonAction("empty.service.permissionAction") {
            @Override
            public Object action() {
                //do nothing
                return true;
            }
        };
        final IAction persistableAction = new CommonAction("empty.service.persistableAction") {
            @Override
            public Object action() {
                return Serializable.class.isAssignableFrom((Class) getParameter()[0]);
            }
        };
        final IAction executeAction = new CommonAction("empty.service.executeAction") {
            @Override
            public Integer action() {
                return null;
            }
        };
        BeanContainer.initServiceActions(idFinder,
            relationFinder,
            lazyRelationResolver,
            saveAction,
            deleteAction,
            exampleFinder,
            betweenFinder,
            queryFinder,
            queryMapFinder,
            attrAction,
            permissionAction,
            persistableAction,
            executeAction);
        self.emptyServices = true;
    }

    /**
     * @return see {@link #emptyServices}
     */
    public static boolean isConnected() {
        return isInitialized() && !self().emptyServices;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  T createBean(Class type) {
        return createBeanInstance(type);
    }

    /**
     * createBeanInstance
     * 
     * @param 
     * @param type
     * @return
     */
    public static  T createBeanInstance(Class type) {
        try {
            T bean;
            if (type.isInterface()) {
                bean = BeanProxy.createBeanImplementation(type, null, null, Thread.currentThread()
                    .getContextClassLoader());
            } else {
                bean = BeanClass.createInstance(type);
            }
            initDefaults(bean);
            return bean;
        } catch (final Exception e) {
            ManagedException.forward(e);
            return null;
        }
    }

    /**
     * initializes collections not to be null.
     * @param bean new created bean.
     */
    public static  void initDefaults(T bean) {
        //to fulfill bindings on onetomany, we bind empty lists
        final BeanClass clazz = BeanClass.getBeanClass(bean.getClass());
        final Collection multiValueAttributes = clazz.getMultiValueAttributes();
        for (final BeanAttribute beanAttribute : multiValueAttributes) {
            if (Set.class.isAssignableFrom(beanAttribute.getType())) {
                beanAttribute.setValue(bean, new LinkedHashSet());
            } else if (Collection.class.isAssignableFrom(beanAttribute.getType())) {
                beanAttribute.setValue(bean, new LinkedList());
            } else if (Properties.class.isAssignableFrom(beanAttribute.getType())) {
                beanAttribute.setValue(bean, new Properties());
            } else if (Map.class.isAssignableFrom(beanAttribute.getType())) {
                beanAttribute.setValue(bean, new LinkedHashMap());
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  void delete(T bean) {
        deleteAction.setParameter(new Object[] { bean });
        deleteAction.activate();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  T save(T bean) {
        saveAction.setParameter(new Object[] { bean });
        return (T) saveAction.activate();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  T getByID(Class type, Object id) {
        idFinderAction.setParameter(new Object[] { type, id });
        return (T) idFinderAction.activate();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  Collection getBeans(Class type, int startIndex, int maxResult) {
        typeFinderAction.setParameter(new Object[] { type, startIndex, maxResult });
        return (Collection) typeFinderAction.activate();
    }

    @Override
    public  Collection getBeans(BeanFindParameters parameters) {
        typeFinderAction.setParameter(new Object[] { parameters });
        return (Collection) typeFinderAction.activate();
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public  Collection getBeansByExample(T exampleBean) {
        return getBeansByExample(exampleBean, false, 0, Integer.MAX_VALUE);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  Collection getBeansByExample(T exampleBean, Boolean useLike, int startIndex, int maxResult) {
        exampleFinderAction.setParameter(new Object[] { exampleBean, useLike, startIndex, maxResult });
        return (Collection) exampleFinderAction.activate();
    }

    @Override
    public  Collection getBeansByExample(T exampleBean, Boolean useLike, BeanFindParameters parameters) {
        exampleFinderAction.setParameter(new Object[] { exampleBean, useLike, parameters });
        return (Collection) exampleFinderAction.activate();
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    public  Collection getBeansBetween(T firstExampleBean, T secondExampleBean, int startIndex, int maxResult) {
        betweenFinderAction.setParameter(new Object[] { firstExampleBean, secondExampleBean, startIndex, maxResult });
        return (Collection) betweenFinderAction.activate();
    }

    @Override
    public  Collection getBeansBetween(T firstBean, T secondBean, BeanFindParameters parameters) {
        betweenFinderAction.setParameter(new Object[] { firstBean, secondBean, parameters });
        return (Collection) betweenFinderAction.activate();
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    public  T resolveLazyRelations(T bean) {
        lazyrelationInstantiateAction.setParameter(new Object[] { bean });
        return (T) lazyrelationInstantiateAction.activate();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IAttributeDef getAttributeDef(Object bean, String attributeName) {
        attrdefAction.setParameter(new Object[] { bean, attributeName });
        return attrdefAction.activate();
    }

    @Override
    public Boolean hasPermission(String roleName, String action) {
        permissionAction.setParameter(new Object[] { roleName, action });
        return (Boolean) permissionAction.activate();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isPersistable(Class beanClass) {
        persistableAction.setParameter(new Object[] { beanClass });
        return (Boolean) persistableAction.activate();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  Collection getBeansByQuery(String query, Boolean nativeQuery, Object[] args, Class... lazyRelations) {
        queryAction.setParameter(new Object[] { query, nativeQuery, args, lazyRelations });
        return (Collection) queryAction.activate();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  Collection getBeansByQuery(String query,
            Boolean nativeQuery,
            Map par,
            Class... lazyRelations) {
        queryMapAction.setParameter(new Object[] { query, nativeQuery, par, lazyRelations });
        return (Collection) queryMapAction.activate();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Integer executeStmt(String query, Boolean nativeQuery, Object[] args) {
        executeAction.setParameter(new Object[] { query, nativeQuery, args });
        return executeAction.activate();
    }

    /**
     * isConstraintError
     * 
     * @param e server exception
     * @return true, if error-cause contains a constraint violation
     */
    public static boolean isConstraintError(Exception e) {
        //etwas unsauber!
        Throwable cause = e;
        Throwable c = cause;
        while (c != null) {
            cause = c;
            c = c.getCause();
        }
        if (cause.toString().contains("onstraint")) {
            return true;
        }
        return false;
    }

    /**
     * convenience method to ask for a user role depending on a bean presenters action.
     * 
     * @param beanType bean class. this should be a specific class like an entity - no collection of entities!
     * @param packedInList true, if the bean type is packed into a collection. e.g. in
     *            BeanContainerView/BeanContainerDialog.
     * @param actionName translated action name.
     * @return true, if user has role for that action, false otherwise
     */
    public static final boolean hasPermission(Class beanType, boolean packedInList, String actionName) {
        return instance().hasPermission(getActionId(beanType, packedInList, actionName), null);
    }

    /**
     * data rows for the given type
     * 
     * @param beanType bean type
     * @return count(*) for type
     */
    public static final long getCount(Class beanType) {
        return ((Number) instance()
            .getBeansByQuery("select count(*) from " + beanType.getSimpleName(), true, (Object[]) null).iterator()
            .next()).longValue();
    }

    /**
     * getActionId
     * 
     * @param beanType presenters bean type to create an action for
     * @param packedInList whether it is a bean instance or a bean container
     * @param actionName simple action name
     * @return full action id
     */
    public static final String getActionId(Class beanType, boolean packedInList, String actionName) {
        final String type =
            BeanClass.getName(beanType).toLowerCase() + (packedInList ? Messages.getString("tsl2nano.list")
                .toLowerCase()
                : "");
        return type + "." + actionName.toLowerCase();
    }

    /**
     * will search for the actionId in the resource bundle - then in tsl2nano base bundle - if not found, the action
     * simple name will be returned
     * 
     * @param actionId full action id
     * @param actionSimpleName simple action name
     * @param if true, the tooltip for the action will be evaluated
     * @return text to show on action
     */
    public static final String getActionText(String actionId, boolean tooltip) {
        final String tooltip_postfix = (tooltip ? Messages.POSTFIX_TOOLTIP : "");
        final String actionSimpleName = actionId.substring(actionId.lastIndexOf(".") + 1);
        String text = Messages.getString(actionId + tooltip_postfix);
        if (Messages.unknown(text)) {
            text = Messages.getString("tsl2nano." + actionSimpleName + tooltip_postfix);
            if (Messages.unknown(text)) {
                return StringUtil.toFirstUpper(actionSimpleName);
            } else if (tooltip) {
                return "";
            } else {
                return text;
            }
        }
        return text;
    }

    /**
     * evaluates the given entities bean id. don't call that method, if the bean is not an ejb entity!
     * 
     * @param beanOrClass entity bean or type to evaluate
     * @return bean id attribute or null, if not existent
     */
    public static BeanAttribute getIdAttribute(Object beanOrClass) {
        if (!isInitialized()) {
            return null;
        }
        Class cls =
            (Class) (beanOrClass == null || beanOrClass instanceof Class ? beanOrClass : beanOrClass.getClass());
        if (cls == null || !instance().isPersistable(cls))
            return null;
        final BeanClass bc = BeanClass.getBeanClass(cls);
        final Collection attributes = bc.getSingleValueAttributes();
        for (final BeanAttribute attr : attributes) {
            final IAttributeDef attributeDef = BeanContainer.instance().getAttributeDef(cls, attr.getName());
            if (attributeDef != null && attributeDef.id()) {
                return attr;
            }
        }
        return null;
    }

    /**
     * see {@link #getIdAttribute(Object)}
     * 
     * @param bean instance
     * @return true, if bean is persistable and id attribute is null
     */
    public boolean isTransient(Object bean) {
        final BeanAttribute idAttribute = getIdAttribute(bean);
        Object id;
        if (idAttribute != null && (id = idAttribute.getValue(bean)) != null) {
            Class type = idAttribute.getDeclaringClass();
            return !isPersistable(type) || getByID(type, id) == null;
        } else {
            return true;
        }
    }

    //TODO: implement getValueBetween using new action
//    public static final  Collection getValueBetween(String attributeName, ClassattributeType, T first, T second) {
//        ServiceUtil.
//        BeanContainer.instance().getBeansBetween(first, second, -1);
//        Collection result;
//        
//    }
    public static void reset() {
        self = null;
        selfThread.remove(); //how to remove all other thread-values?
    }

    /**
     * reloads all referenced entities (having only an id)
     * 
     * @param obj transient (example) entity holding attached entities as relations
     * @return the obj itself
     */
    public static Object attachEntities(Object obj) {
        List> attributes = Bean.getBean(obj).getBeanAttributes();
        BeanValue bv;
        for (IAttributeDefinition a : attributes) {
            if (a.isRelation() && (bv = (BeanValue) a).getValue() != null) {
                Bean b = Bean.getBean(bv.getValue());
                Object n = BeanContainer.instance().getByID(bv.getType(), b.getId());
                bv.setValue(n);
            }
        }
        return obj;
    }

    /**
     * replaces attached entities with copies holding only the id
     * 
     * @param obj transient (example) entity holding attached entities as relations
     * @return the obj itself
     */
    public static Object detachEntities(Object obj) {
        List> attributes = Bean.getBean(obj).getBeanAttributes();
        BeanValue bv;
        for (IAttributeDefinition a : attributes) {
            if (a.isRelation() && (bv = (BeanValue) a).getValue() != null) {
                Bean b = Bean.getBean(bv.getValue());
                Object n = BeanClass.createInstance(a.getType());
                Bean.getBean(n).getIdAttribute().setValue(n, b.getId());
                bv.setValue(n);
            }
        }
        return obj;
    }

    /**
     * creates and sets a generated value for the id. if jpa annotation @GenerateValue is present, it will overwrite this id.
     * 
     * @param newItem
     */
    public static void createId(Object newItem) {
        if (ENV.get("value.id.fill.uuid", true)) {
            final BeanAttribute idAttribute = BeanContainer.getIdAttribute(newItem);
            if (idAttribute != null) {
                Object value = null;
                if (String.class.isAssignableFrom(idAttribute.getType())) {
                    IAttributeDef def = ENV.get(IBeanContainer.class).getAttributeDef(newItem,
                        idAttribute.getName());
                    //TODO: through string cut, the uuid may not be unique
                    value =
                        StringUtil.fixString(BeanUtil.createUUID(), (def.length() > -1 ? def.length() : 0), ' ', true);
                } else if (NumberUtil.isNumber(idAttribute.getType())) {
                    //subtract the years from 1970 to 2015 to be castable to an int
                    //TODO: use a more unique value
                    if (ENV.get("value.id.use.timestamp", false)) {
                        value = System.currentTimeMillis();
                        if (NumberUtil.isInteger(idAttribute.getType())) {
                            value = DateUtil.getMillisWithoutYear((Long) value);
                        }
                    } else {
                        value = ENV.counter("collector.new.id.number.counter.start", 1);
                    }
                } else if (ENV.get("container.try.generate.multi.primary.key", true) 
                        && (idAttribute.getType().getPackage() == null || idAttribute.getType().getPackage().equals(newItem.getClass().getPackage()))) {
                    value = createCompositeKey(newItem, idAttribute);
                    if (value == null)
                        LOG.warn("the id-attribute " + idAttribute + " can't be created for "
                                + idAttribute.getType());
                } else {
                    LOG.warn("the id-attribute " + idAttribute + " can't be assigned to a generated value of type "
                        + idAttribute.getType());
                }
                idAttribute.setValue(newItem, value);
                return;
            }
        }
        LOG.debug("no id will be generated for new item " + newItem);
    }

    /**
     * tries to create the 
     * @param newItem
     * @param idAttribute
     * @return
     */
    private static Object createCompositeKey(Object newItem, BeanAttribute idAttribute) {
        Object value = BeanClass.createInstance(idAttribute.getType());
        //TODO: implement going through all item attributes, get their idAttributes and try to set them to idValue
        Bean bean = Bean.getBean(newItem);
        BeanDefinition idAttrs = BeanDefinition.getBeanDefinition(idAttribute.getType());
        IAttribute originAttr;
        Object originValue;
        for (Object idAttrObj : idAttrs.getAttributes()) {
            IAttribute idAttr = (IAttribute) idAttrObj;
            try {
                originAttr = bean.getAttribute(idAttr.getName());
                if (originAttr != null) {
                    originValue = originAttr.getValue(newItem);
                    if (originValue != null)
                        idAttr.setValue(value, getIdAttribute(originValue).getValue(originValue));
                }
            } catch (Exception ex) {
                LOG.error("can't generate composite key for " + idAttribute);
                return null;
            }
        }
        return value;
    }
}