
de.tsl2.nano.bean.def.BeanCollector Maven / Gradle / Ivy
/*
* 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 {
/** 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;
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();
}
}
if (hasMode(MODE_SEARCHABLE) && hasDefaultConstructor(getType())) {
T instance =
getSearchPanelBeans().size() > 0 ? getSearchPanelBeans().iterator().next() : BeanClass
.createInstance(getType());
injectIntoRuleCovers(this, instance);
}
return (B) this;
}
private 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;
return getData(from, to);
}
}
public Collection getData(T from, Object to) {
searchStatus = Messages.getFormattedString("tsl2nano.searchdialog.searchrunning");
ENV.get(Profiler.class).starting(this, getName());
if (!isStaticCollection || (isPersistable() && composition == null)) {
collection = (COLLECTIONTYPE) ((IBeanFinder) beanFinder).getData(from, to);
/*
* 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());
sort();
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;
}
}
/**
* @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.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]));
}
/**
* 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, "new");
newAction = new SecureAction
© 2015 - 2025 Weber Informatics LLC | Privacy Policy