de.tsl2.nano.bean.def.BeanCollector 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 20, 2012
*
* Copyright: (c) Thomas Schneider 2012, all rights reserved
*/
package de.tsl2.nano.bean.def;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Proxy;
import java.sql.Timestamp;
import java.text.Format;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
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 org.apache.commons.logging.Log;
import org.simpleframework.xml.Default;
import org.simpleframework.xml.DefaultType;
import org.simpleframework.xml.Transient;
import org.simpleframework.xml.core.Commit;
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.IAttributeDef;
import de.tsl2.nano.bean.ValueHolder;
import de.tsl2.nano.collection.CollectionUtil;
import de.tsl2.nano.collection.Entry;
import de.tsl2.nano.collection.FilteringIterator;
import de.tsl2.nano.collection.MapEntrySet;
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.exception.Message;
import de.tsl2.nano.core.execution.Profiler;
import de.tsl2.nano.core.log.LogFactory;
import de.tsl2.nano.core.util.DateUtil;
import de.tsl2.nano.core.util.FormatUtil;
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.util.DelegatorProxy;
/**
* see {@link IBeanCollector}. the collector inherits from {@link BeanDefinition}.
*
* @author Thomas Schneider
* @version $Revision$
*/
@SuppressWarnings({ "rawtypes", "unchecked", "serial" })
@Default(value = DefaultType.FIELD, required = false)
public class BeanCollector, T> extends BeanDefinition implements
IBeanCollector {
public static final String ACTION_OPEN = "open";
public static final String ACTION_NEW = "new";
public static final String ACTION_DELETE = "delete";
/** serialVersionUID */
private static final long serialVersionUID = -5260557109700841750L;
private static final Log LOG = LogFactory.getLog(BeanCollector.class);
public static final String POSTFIX_COLLECTOR = ".collector";
public static final String POSTFIX_QUICKSEARCH = "quicksearch";
public static final String KEY_COMMANDHANDLER = "bean.commandhandler";
/** static data or current data representation of this beancollector */
protected transient COLLECTIONTYPE collection;
protected transient boolean isStaticCollection = false;
/** defines the data for this collector through it's getData() method */
protected transient IBeanFinder beanFinder;
/** holds the current selection */
protected transient ISelectionProvider selectionProvider;
/**
* holds the connection to the composition parent. if not null, the beancollector will work only on items having a
* connection to this composition (uml-composition where childs can't exist without it's parent!).
*/
@Transient
protected Composition composition;
protected transient IAction> newAction;
protected transient IAction> editAction;
protected transient IAction> deleteAction;
/** the extending class has to set an instance for the ok action to set it as default button */
protected transient IAction> openAction;
/** defines the behavior and the actions of the beancollector */
@Transient
protected int workingMode = MODE_EDITABLE | MODE_CREATABLE | MODE_SEARCHABLE;
/**
* search panel instructions.
*/
protected transient IAction searchAction;
protected transient IAction quickSearchAction;
protected transient IAction> resetAction;
protected transient String searchStatus = INIT_SEARCH_STATUS;
static final String INIT_SEARCH_STATUS = Messages.getString("tsl2nano.searchdialog.nosearch");
/** whether to refresh data from beancontainer before opening edit-dialog */
@Transient
protected boolean reloadBean = true;
// @ElementList(name = "column", inline = true, required = false/*, type=ValueColumn.class*/)
private transient Collection columnDefinitions;
/** temporary variable to hold the {@link #toString()} output (--> performance) */
private transient String asString;
/** on each activation, we do a count on the database */
private transient long lastCount = -2;
/**
* the beancollector should listen to any change of the search-panel (beanfinders range-bean) to know which action
* should have the focus
*/
private transient Boolean hasSearchRequestChanged;
/** pointer to the current row to be build/painted/evaluated */
private transient Iterator iterator = null;
/**
* constructor. should only used by framework - for de-serialization.
*/
public BeanCollector() {
super();
}
/**
* see constructor {@link #CollectionEditorBean(IBeanFinder, boolean, boolean, boolean)}.
*/
public BeanCollector(Class beanType, int workingMode) {
this(new BeanFinder(beanType), workingMode, null);
}
/**
* see constructor {@link #CollectionEditorBean(IBeanFinder, boolean, boolean, boolean)}.
*/
public BeanCollector(final COLLECTIONTYPE collection, int workingMode) {
this(collection, workingMode, null);
}
/**
* see constructor {@link #CollectionEditorBean(IBeanFinder, boolean, boolean, boolean)}.
*/
public BeanCollector(final COLLECTIONTYPE collection, int workingMode, Composition composition) {
this((Class) collection.iterator().next().getClass(), collection, workingMode, composition);
}
/**
* see constructor {@link #CollectionEditorBean(IBeanFinder, boolean, boolean, boolean)}.
*/
public BeanCollector(Class beanType, final COLLECTIONTYPE collection, int workingMode, Composition composition) {
this(new BeanFinder(beanType), workingMode, composition);
this.collection = collection;
if (isStaticCollection || !hasMode(MODE_SEARCHABLE)) {
this.searchStatus = "";
}
}
/**
* constructor
*
* @param beanFinder implementation to evaluate the data
* @param workingMode one of {@link IBeanCollector#MODE_EDITABLE}, {@link IBeanCollector#MODE_CREATABLE} etc. Please
* see {@link IBeanCollector} for more modes.
*/
public BeanCollector(IBeanFinder beanFinder, int workingMode, Composition composition) {
super(beanFinder.getType());
init(null, beanFinder, workingMode, composition);
}
/**
* init
*
* @param beanFinder implementation to evaluate the data
* @param workingMode one of {@link IBeanCollector#MODE_EDITABLE}, {@link IBeanCollector#MODE_CREATABLE} etc. Please
* see {@link IBeanCollector} for more modes.
*/
protected void init(COLLECTIONTYPE collection,
IBeanFinder beanFinder,
int workingMode,
Composition composition) {
// setName(Messages.getString("tsl2nano.list") + " " + getName());
this.isStaticCollection = !Util.isEmpty(collection) ||
beanFinder == null || (!isPersistable());
this.collection = collection != null ? collection : (COLLECTIONTYPE) new LinkedList();
setBeanFinder(beanFinder);
//TODO: the check for attribute can't be done here - perhaps attributes will be added later!
// if (getAttributeDefinitions().size() == 0) {
// LOG.warn("bean-collector without showing any attribute");
// this.workingMode = 0;
// searchStatus = Messages.getFormattedString("tsl2nano.login.noprincipal", "...", "...");
// } else {
this.workingMode = workingMode;
// }
if (isStaticCollection) {
searchStatus =
Messages.getFormattedString("tsl2nano.searchdialog.searchresultcount", this.collection.size());
}
this.composition = composition;
if (composition != null && composition.getTargetType() == null) {
if (hasMode(MODE_SEARCHABLE)) {
LOG.warn("removing MODE_SEARCHABLE - because it is a composition");
removeMode(MODE_SEARCHABLE);
}
}
// if (beanFinder != null) {
actions = new LinkedHashSet();
if (hasMode(MODE_SEARCHABLE)) {
createSearchAction();
if (hasFilter()) {
createResetAction();
}
}
if (hasMode(MODE_EDITABLE)) {
createOpenAction();
}
if (hasMode(MODE_CREATABLE)) {
createNewAction();
createDeleteAction();
}
// }
assignColumnValues();
if (ENV.get("beandef.autoinit", true))
autoInit(name);
}
/**
* the beancollector instance itself should not be saved - this is done by its underlaying beandefinition
*/
@Override
protected boolean isSaveable() {
return false;//getClass().equals(BeanCollector.class) ? false : super.isSaveable();
}
/**
* getBeanType
*
* @return optional beanType (content type of collection)
*/
protected Class getType() {
//try to evaluate the collection content type
if (beanFinder == null) {
if (collection != null && collection.size() > 0) {
setBeanFinder(new BeanFinder(collection.iterator().next().getClass()));
} else {
setBeanFinder(new BeanFinder(super.getClazz()));
}
}
return beanFinder.getType();
}
@Override
public boolean isPersistable() {
return BeanContainer.isInitialized() && BeanContainer.instance().isPersistable(getType());
}
@Override
public > B onActivation(Map context) {
super.onActivation(context);
iterator = null;
doAutomaticSearch();
return (B) this;
}
public void doAutomaticSearch() {
if (!isStaticCollection && Util.isEmpty(collection)) {
long countCheck = ENV.get("collector.search.auto.count.lowerthan", 20);
boolean dosearch = countCheck > 0 && count() < countCheck;
//if at least one column has a search value, we start the search directly
if (!dosearch) {
Collection columns = getColumnDefinitions();
for (IPresentableColumn c : columns) {
if (c.getMinSearchValue() != null || c.getMaxSearchValue() != null) {
dosearch = true;
break;
}
}
}
if (dosearch) {
getBeanFinder().getData();
}
}
}
protected long count() {
long count = -1;
try {
count =
!Util.isEmpty(collection) ? collection.size() : BeanContainer.instance().isPersistable(getType())
? BeanContainer.getCount(getType()) : -1;
} catch (Exception ex) {
LOG.warn(getName() + " is declared as @ENTITY but has no mapped TABLE --> can't evaluate count(*)!");
}
if (count != lastCount)
asString = null;
lastCount = count;
return lastCount;
}
@Override
public void onDeactivation(Map context) {
super.onDeactivation(context);
iterator = null;
/*
* if a bean-collector was left through cancel, new created compositions must be removed!
*/
if (composition != null) {
for (T instance : collection) {
Bean.getBean((Serializable) instance).onDeactivation(context);
}
} else {
if (ENV.get("collector.ondeactivation.selection.clear", true))
getSelectionProvider().getValue().clear();
}
}
/**
* {@inheritDoc}
*/
@Override
public IBeanFinder getBeanFinder() {
return beanFinder;
}
/**
* @param beanFinder The beanFinder to set.
*/
public void setBeanFinder(final IBeanFinder beanFinder) {
if (beanFinder != null) {
/*
* to use the beancollectors collection, we wrap the given beanfinder
*/
@SuppressWarnings("unused")
Object internalBeanFinder = new Serializable() {
boolean betweenFinderCreated = false;
String lastExpression = null;
//used by proxy!
Collection getData() {
if (lastExpression != null) {
return getData(lastExpression);
} else {
Collection searchPanelBeans = getSearchPanelBeans();
Iterator it = searchPanelBeans.iterator();
T from = searchPanelBeans.size() > 0 ? it.next() : null;
T to = searchPanelBeans.size() > 1 ? it.next() : null;
String[] orderByIndexes = getOrderByColumns();
return getData(from, to);
}
}
public Collection getData(T from, Object to) {
searchStatus = Messages.getFormattedString("tsl2nano.searchdialog.searchrunning");
ENV.get(Profiler.class).starting(this, getName());
Message.send(searchStatus);
if (!isStaticCollection || (isPersistable() && composition == null)) {
collection = (COLLECTIONTYPE) ((IBeanFinder) beanFinder).getData(from, to, getOrderByColumns());
/*
* if it is a composition, all data has to be found in the compositions-parent-container
*/
if (composition != null) {
for (Iterator it = collection.iterator(); it.hasNext();) {
T item = it.next();
if (!composition.getParentContainer().contains(item)) {
it.remove();
}
}
}
} else if (!betweenFinderCreated) {
collection =
CollectionUtil.getFilteringBetween(collection, from, (T) to, true);
betweenFinderCreated = true;
}
/*
* respect authorization/permissions
*/
Collection result = authorized(collection);
searchStatus = Messages.getFormattedString("tsl2nano.searchdialog.searchresultcount",
result.size());
Message.send(searchStatus);
sort();
// //TODO: this will be a performance issue!
// Message.send(" injecting new objects for rulecovers...");
// if (hasMode(MODE_SEARCHABLE) && hasDefaultConstructor(getType())) {
// T instance =
// getSearchPanelBeans().size() > 0 ? getSearchPanelBeans().iterator().next() : BeanClass
// .createInstance(getType());
// injectIntoRuleCovers(BeanCollector.this, instance);
// }
// for (T o : collection) {
// injectIntoRuleCovers(Bean.getBean(o), o);
// }
// Message.send(searchStatus);
return result;
}
public Collection getData(String expression) {
if (!isStaticCollection) {
collection =
(COLLECTIONTYPE) getValueExpression().matchingObjects(expression);
} else if (!betweenFinderCreated) {
collection =
CollectionUtil.getFiltering(collection, new StringBuilder(expression));
betweenFinderCreated = true;
}
Collections.sort((List) collection, getValueExpression().getComparator());
return authorized(collection);
}
private Collection authorized(COLLECTIONTYPE collection) {
if (ENV.get("collector.check.permission.data", true)) {
return CollectionUtil.getFiltering(collection, new IPredicate() {
@Override
public boolean eval(T arg0) {
return getAttributeDefinitions().size() > 0 && BeanContainer.instance()
.hasPermission(
BeanClass.getDefiningClass(arg0.getClass()).getSimpleName().toLowerCase() + "."
+ getValueExpression().to(arg0),
"read");
}
});
} else {
return collection;
}
}
public Collection previous() {
return (collection = (COLLECTIONTYPE) beanFinder.previous());
}
public Collection next() {
return (collection = (COLLECTIONTYPE) beanFinder.next());
}
};
this.beanFinder = DelegatorProxy.delegator(IBeanFinder.class, internalBeanFinder, beanFinder);
} else {
this.beanFinder = null;
}
}
protected String[] getOrderByColumns() {
Integer[] indexes = getSortIndexes();
String sIndexes[] = new String[indexes.length];
for (int i = 0; i < indexes.length; i++) {
IPresentableColumn col = getColumn(indexes[i]);
boolean up = col.isSortUpDirection();
sIndexes[i] = (up ? "+": "-") + col.getName();
}
return sIndexes;
}
/**
* @return Returns the selectionProvider.
*/
@Override
public ISelectionProvider getSelectionProvider() {
if (selectionProvider == null) {
selectionProvider = new SelectionProvider(new LinkedList());
}
return selectionProvider;
}
/**
* @param selectionProvider The selectionProvider to set.
*/
public void setSelectionProvider(ISelectionProvider selectionProvider) {
this.selectionProvider = selectionProvider;
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasMode(int mode) {
return NumberUtil.hasBit(workingMode, mode);
}
/**
* {@inheritDoc}
*/
@Override
public void setMode(int mode) {
this.workingMode = mode;
}
@Override
public void addMode(int modebit) {
this.workingMode |= modebit;
}
@Override
public void removeMode(int modebit) {
workingMode = NumberUtil.filterBits(workingMode, modebit);
}
/**
* getWorkingMode
*
* @return
*/
public int getWorkingMode() {
return workingMode;
}
/**
* getFirstSelectedElement
*
* @return first selection element or null
*/
protected T getFirstSelectedElement() {
if (getSelectionProvider() == null) {
return null;
}
Collection selection = getSelectionProvider().getValue();
return selection.isEmpty() ? null : selection.iterator().next();
}
/**
* hasSelection
*
* @return tgrue, if at least one element was selected
*/
public boolean hasSelection() {
return getFirstSelectedElement() != null;
}
/**
* hasFilter
*
* @return true, if bean-finder could create an filter-range instance
*/
public boolean hasFilter() {
return getBeanFinder() != null && getBeanFinder().getFilterRange() != null;
}
/**
* hasSearchRequestChanged
*
* @return true, if at least one value of range-bean from or to changed.
*/
protected boolean hasSearchRequestChanged() {
return hasSearchRequestChanged != null ? hasSearchRequestChanged : false;
}
/**
* wasActivated
*
* @return true, if onActivation() was called before.
*/
public boolean wasActivated() {
return lastCount > -2;
}
/**
* setSelectedElement
*
* @param selected
*/
public void setSelected(T... selected) {
Collection selection = getSelectionProvider().getValue();
selection.clear();
if (selected != null) {
for (int i = 0; i < selected.length; i++) {
selection.add(selected[i]);
}
}
}
public void selectFirstElement() {
if (getSelectionProvider() == null || collection == null || collection.isEmpty()) {
LOG.warn("couldn't select first element - no data or selection-provider available yet!");
return;
}
setSelected(collection.iterator().next());
}
/**
* @return Returns the reloadBean.
*/
public boolean isReloadBean() {
return reloadBean;
}
/**
* @param reloadBean The reloadBean to set.
*/
public void setReloadBean(boolean reloadBean) {
this.reloadBean = reloadBean;
}
/**
* {@inheritDoc}
*/
@Override
public void refresh() {
//nothing to do on default implementation
}
@Override
public BeanCollector refreshed() {
if (isStale())
return getBeanCollector(collection, workingMode);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public Object editItem(Object item) {
return getPresentationHelper().startUICommandHandler(item);
}
/**
* {@inheritDoc}
*/
@Override
public void checkBeforeDelete(Collection selection) {
}
/**
* {@inheritDoc}
*/
@Override
public void deleteItem(T item) {
if (!CompositionFactory.markToPersist(item) && Bean.getBean((Serializable) item).getId() != null) {
BeanContainer.instance().delete(item);
}
}
/**
* {@inheritDoc}
*/
@Override
public T createItem(T selectedItem) {
T newItem = null;
Class type = getType();
/*
* do a copy of the selected element (only if exactly one element was selected) and the property was set.
*/
if (selectedItem != null && getSelectionProvider() != null && getSelectionProvider().getValue().size() == 1
&& ENV.get("collector.new.clone.selected", true)
&& !(Entry.class.isAssignableFrom(type))) {
try {
/*
* don't copy composition or cascading fields!
*/
newItem = copySimpleValues(selectedItem);
BeanUtil.createOwnCollectionInstances(newItem);
} catch (final Exception e) {
LOG.error(e);
Message.send("Couldn't copy selected element!");
}
}
if (newItem == null) {
/*
* there is no information how to create a new bean - at least one stored bean instance must exist!
*/
if (type != null && Collection.class.isAssignableFrom(type)) {
LOG.warn(
"There is no information how to create a new bean - at least one stored bean instance must exist!");
return null;
} else if (Entry.class.isAssignableFrom(type)) {
// normally we would handle this inside the generic else block, but we need
// the generics key and value type informations
if (Proxy.isProxyClass(collection.getClass())) {
newItem = (T) ((MapEntrySet) (FilteringIterator.getIterable((Proxy) collection))).add(null, null);
} else {
newItem = (T) ((MapEntrySet) (collection)).add(null, null);
}
} else {
newItem = BeanContainer.instance().createBean(getType());
}
setDefaultValues(newItem);
}
/*
* if timestamp fields are not shown, generate new timestamps
*/
if (ENV.get("value.timestamp.default", true)) {
//respect all attributes
BeanClass bc = BeanClass.getBeanClass(getDeclaringClass());
List attrs = bc.getAttributes();
// Map> attrs = getAttributeDefinitions();
Timestamp ts = new Timestamp(DateUtil.cutSeconds(System.currentTimeMillis()));
for (IAttribute a : attrs) {
IAttributeDef def = BeanContainer.instance().getAttributeDef(newItem, a.getName());
if (def != null && !def.nullable() && def.temporalType() != null
&& Timestamp.class.isAssignableFrom(def.temporalType())) {
// if (a instanceof IValueAccess) {
// ((IValueAccess) a).setValue(ts);
// } else {
a.setValue(newItem, ts);
// }
}
}
}
/*
* the id attribute of the selected bean must not be copied!!!
* we create a generated value for the id. if jpa annotation @GenerateValue
* is present, it will overwrite this id.
*/
BeanContainer.createId(newItem);
/*
* assign the new item to the composition parent. this should be done last,
* because of the equals/hash difference to existing items.
*/
if (composition != null) {
composition.add(newItem);
}
return newItem;
}
private T copySimpleValues(T selectedItem) {
List names = new ArrayList(getAttributeNames().length);
IAttributeDefinition attr;
for (IAttribute a : getSingleValueAttributes()) {
if (isPersistable() && a.isVirtual())
continue;
else if (a instanceof IAttributeDefinition) {
attr = (IAttributeDefinition) a;
if (attr.isMultiValue() || attr.composition() || attr.cascading()) {
LOG.debug(
"didn't copy attribute " + attr + " to new item in cause of being a composition or oneToMany");
continue;
}
}
names.add(a.getName());
}
return copyValues(selectedItem, createInstance(), true, false, names.toArray(new String[0]));
}
@Override
public IAction> getActionByName(String name) {
return getAction(BeanContainer.getActionId(getType(), true, name));
}
/**
* creates the new button with listeners inside the parent component
*
* @param dform parent of search button
*/
protected void createNewAction() {
final String actionId = BeanContainer.getActionId(getType(), true, ACTION_NEW);
newAction = new SecureAction