package org.tynamo.base;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.tapestry5.annotations.InjectComponent;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.Persist;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.grid.GridDataSource;
import org.apache.tapestry5.ioc.Messages;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.tynamo.components.SearchFilters;
import org.tynamo.descriptor.CollectionDescriptor;
import org.tynamo.descriptor.EmbeddedDescriptor;
import org.tynamo.descriptor.ObjectReferenceDescriptor;
import org.tynamo.descriptor.TynamoClassDescriptor;
import org.tynamo.descriptor.TynamoPropertyDescriptor;
import org.tynamo.descriptor.extension.BeanModelExtension;
import org.tynamo.search.SearchFilterOperator;
import org.tynamo.search.SearchFilterPredicate;
import org.tynamo.services.DescriptorService;
import org.tynamo.services.SearchableGridDataSourceProvider;
import org.tynamo.util.DisplayNameUtils;
public abstract class GenericModelSearch {
@Inject
private DescriptorService descriptorService;
@Inject
SearchableGridDataSourceProvider gridDataSourceProvider;
@Inject
private Messages messages;
@InjectComponent
private SearchFilters searchFilters;
@Parameter(required = true, allowNull = false)
private Class beanType;
@Persist
private String searchTerms;
@Persist
private Map<Class, SortedMap<TynamoPropertyDescriptor, SearchFilterPredicate>> filterStateByBeanType;
@Property(write = false)
private SortedMap<TynamoPropertyDescriptor, SearchFilterPredicate> displayableFilterDescriptorMap;
private List<TynamoPropertyDescriptor> searchablePropertyDescriptors;
private GridDataSource gridDataSource;
public Class getBeanType() {
return beanType;
}
public SearchableGridDataSourceProvider getGridDataSourceProvider() {
return gridDataSourceProvider;
}
void setupRender() {
doPrepare();
}
// use onPrepareForSubmit + setupRender rather than onPrepare to initialize displayableDescriptorMap in time to decide
// whether to hide the whole form
// void onPrepareSearchFilterForm() {
void onPrepareForSubmitFromSearchFilterForm() {
doPrepare();
}
protected void doPrepare() {
if (displayableFilterDescriptorMap != null && searchablePropertyDescriptors != null) return;
TynamoClassDescriptor classDescriptor = descriptorService.getClassDescriptor(beanType);
if (filterStateByBeanType == null) filterStateByBeanType = Collections
.synchronizedMap(new HashMap<Class, SortedMap<TynamoPropertyDescriptor, SearchFilterPredicate>>());
else displayableFilterDescriptorMap = filterStateByBeanType.get(beanType);
if (displayableFilterDescriptorMap == null || searchablePropertyDescriptors == null) {
SortedMap<TynamoPropertyDescriptor, SearchFilterPredicate> map =
new TreeMap<TynamoPropertyDescriptor, SearchFilterPredicate>(new TynamoPropertyDescriptorComparator());
List<TynamoPropertyDescriptor> descriptors = new ArrayList<TynamoPropertyDescriptor>();
for (TynamoPropertyDescriptor descriptor : classDescriptor.getPropertyDescriptors()) {
// FIXME remove all strings for now, decide how to deal with them later
// TODO perhaps we should create type-specfic default for SearchFilterPredicates?
// create a new method createSearchFilterPredicate(descriptor)
if (descriptor.isNonVisual() || descriptor.isIdentifier() || !descriptor.isSearchable()) continue;
if (isUsedAsSearchFilter(classDescriptor, descriptor)) {
// at least for now, don't allow creating filters for complex properties
if (descriptor instanceof ObjectReferenceDescriptor || descriptor instanceof CollectionDescriptor
|| descriptor instanceof EmbeddedDescriptor || descriptor.supportsExtension(BeanModelExtension.class))
continue;
map.put(descriptor, createSearchFilterPredicate(descriptor.getPropertyType()));
}
else descriptors.add(descriptor);
}
if (displayableFilterDescriptorMap == null) {
filterStateByBeanType.put(beanType, map);
displayableFilterDescriptorMap = map;
}
searchablePropertyDescriptors = descriptors;
}
}
protected SearchFilterPredicate createSearchFilterPredicate(Class propertyType) {
SearchFilterPredicate predicate = new SearchFilterPredicate();
predicate.setOperator(SearchFilterOperator.eq);
if (boolean.class.isAssignableFrom(propertyType)) {
predicate.setLowValue(Boolean.FALSE);
} else if (Boolean.class.isAssignableFrom(propertyType)) {
predicate.setLowValue(Boolean.FALSE);
} else if (String.class.isAssignableFrom(propertyType)) {
predicate.setOperator(SearchFilterOperator.contains);
}
return predicate;
}
public boolean isSearchable() {
boolean searchable = descriptorService.getClassDescriptor(beanType).isSearchable();
if (!searchable) return false;
// hide the search field if there are no results
return isSearchCriteriaSet() || getGridDataSource().getAvailableRows() > 0;
}
public boolean isSearchFiltersAvailable() {
return displayableFilterDescriptorMap != null && displayableFilterDescriptorMap.size() > 0;
}
public boolean isSearchCriteriaSet() {
return getSearchTerms() != null || getActiveFilterMap().size() > 0;
}
public void resetSearchCriteria() {
setSearchTerms(null);
filterStateByBeanType.put(beanType, null);
}
public boolean isUsedAsSearchFilter(TynamoClassDescriptor classDescriptor, TynamoPropertyDescriptor propertyDescriptor) {
// the default implementation simply treats all non-strings as filters
return !propertyDescriptor.isString();
}
public Map<TynamoPropertyDescriptor, SearchFilterPredicate> getActiveFilterMap() {
// may be null if session has expired
if (filterStateByBeanType == null) return Collections.emptyMap();
SortedMap<TynamoPropertyDescriptor, SearchFilterPredicate> descriptorMap = filterStateByBeanType.get(beanType);
if (descriptorMap == null) return Collections.emptyMap();
Map<TynamoPropertyDescriptor, SearchFilterPredicate> activeDescriptorMap = new HashMap<TynamoPropertyDescriptor, SearchFilterPredicate>();
for (Entry<TynamoPropertyDescriptor, SearchFilterPredicate> entry : descriptorMap.entrySet())
if (entry.getValue().isEnabled()) activeDescriptorMap.put(entry.getKey(), entry.getValue());
return activeDescriptorMap;
}
public final GridDataSource getGridDataSource() {
// we *must* initialize the component before returning the dataSource. Since getGridDataSource() is public,
// it's possible it'll be called before setupRender.
doPrepare();
if (gridDataSource == null) gridDataSource = createGridDataSource();
return gridDataSource;
}
protected GridDataSource createGridDataSource() {
if (searchTerms != null) {
return gridDataSourceProvider.createGridDataSource(beanType, getActiveFilterMap(), searchablePropertyDescriptors, searchTerms);
} else {
return gridDataSourceProvider.createGridDataSource(beanType, null, getActiveFilterMap());
}
}
public String getSearchTerms() {
return searchTerms;
}
public void setSearchTerms(String searchTerms) {
this.searchTerms = searchTerms;
}
private class TynamoPropertyDescriptorComparator implements Comparator<TynamoPropertyDescriptor>
{
public int compare(TynamoPropertyDescriptor o1, TynamoPropertyDescriptor o2) {
return DisplayNameUtils.getDisplayName(o1, messages).compareTo(
DisplayNameUtils.getDisplayName(o2, messages));
}
}
}