package com.venky.swf.views.controls.model; import java.io.InputStream; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.BitSet; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import com.venky.core.collections.LowerCaseStringCache; import com.venky.core.log.SWFLogger; import com.venky.core.log.TimerStatistics.Timer; import com.venky.core.util.Bucket; import com.venky.core.util.ObjectUtil; import com.venky.swf.db.Database; import com.venky.swf.db.JdbcTypeHelper.TypeConverter; import com.venky.swf.db.model.Model; import com.venky.swf.db.model.User; import com.venky.swf.db.model.reflection.ModelReflector; import com.venky.swf.path.Path; import com.venky.swf.path._IPath; import com.venky.swf.routing.Config; import com.venky.swf.views.controls.Control; import com.venky.swf.views.controls.model.ModelAwareness.OrderBy; import com.venky.swf.views.controls.page.Link; import com.venky.swf.views.controls.page.layout.Div; import com.venky.swf.views.controls.page.layout.Glyphicon; import com.venky.swf.views.controls.page.layout.Table; import com.venky.swf.views.controls.page.layout.Table.Column; import com.venky.swf.views.controls.page.layout.Table.Row; import com.venky.swf.views.controls.page.text.FileTextBox; import com.venky.swf.views.controls.page.text.Label; import com.venky.swf.views.model.FieldUIMetaProvider; public class ModelListTable<M extends Model> extends Div{ private static final long serialVersionUID = 3569299125414888353L; private Map<String,Integer> maxFieldWidth = new HashMap<String,Integer>() ; private FieldUIMetaProvider metaprovider; public FieldUIMetaProvider getMetaprovider() { return metaprovider; } private ModelAwareness modelAwareness = null; public ModelAwareness getModelAwareness() { return modelAwareness; } private Table table = new Table(); public ModelListTable(Path path, ModelAwareness modelAwareness, FieldUIMetaProvider metaProvider) { addControl(table); addClass("table-responsive"); table.addClass("table-fixedheader"); table.addClass("tablesorter"); this.modelAwareness = modelAwareness; this.metaprovider = metaProvider; } public void addRecords(List<M> records){ Row header = table.createHeader(); BitSet showAction = addHeadingsForLineLevelActions(header); addHeadings(header); for (M record : records) { addRecordToTable(record,showAction); } int numActionsRemoved = 0 ; int numActions = getSingleRecordActions().size(); for (int i = 0 ; i < numActions ; i ++ ){ if (!showAction.get(i)){ table.removeColumn(i-numActionsRemoved); numActionsRemoved ++; // Once a column is removed the indexes are shifted left. } } numActions -= numActionsRemoved; OrderBy orderby = modelAwareness.getOrderBy(); int orderByFieldIndex = getMetaprovider().isFieldVisible(orderby.field) ? getIncludedVisibleFields().indexOf(orderby.field) : -1; if (orderByFieldIndex >= 0 ){ setProperty("sortby", numActions + orderByFieldIndex); setProperty("order", orderby.sortDirection()); } setWidths(header,numActions); } private List<String> getIncludedVisibleFields() { List<String> visibleFields = new ArrayList<String>(); for (String field: getIncludedFields()){ if (getMetaprovider().isFieldVisible(field)){ visibleFields.add(field); } } return visibleFields; } private List<String> getIncludedFields() { return getModelAwareness().getIncludedFields(); } protected void addHeadings(Row headerRow){ List<String> indexedFields = modelAwareness.getReflector().getIndexedFields(); for (String fieldName : getIncludedFields()) { if (!getMetaprovider().isFieldVisible(fieldName)){ continue; } String literal = modelAwareness.getFieldLiteral(fieldName); Column column = headerRow.createColumn(); column.setText(literal); if (indexedFields.contains(fieldName)){ column.addClass("indexed"); column.addControl(new Glyphicon("glyphicon-search","Search would include this column.")); } Integer currentMaxFieldWidth = maxFieldWidth.get(fieldName); if (currentMaxFieldWidth == null || currentMaxFieldWidth < literal.length()){ maxFieldWidth.put(fieldName,literal.length()); } } } private final SWFLogger cat = Config.instance().getLogger(getClass().getName()); protected void setWidths(Row header,int numActions){ Timer timer = cat.startTimer("Setting widths"); try { _setWidths(header, numActions); }finally { timer.stop(); } } protected void _setWidths(Row header,int numActions){ Map<String,Integer> fieldWidthMap = null; //suggestedFieldWidth; if (fieldWidthMap == null){ fieldWidthMap = maxFieldWidth; } List<Column> columns = new ArrayList<Column>(); Bucket total = new Bucket(); Control.hunt(header,Column.class,columns); int i = numActions; total.increment(numActions); final int fieldOffset = 3 ;;// Extra for sorting icon space. for (String field: fieldWidthMap.keySet()){ total.increment(fieldWidthMap.get(field) + fieldOffset); } Bucket pctTotalSoFar = new Bucket(); pctTotalSoFar.increment(numActions); for (Iterator<String> fieldIterator = getIncludedFields().iterator(); fieldIterator.hasNext() ;){ String field = fieldIterator.next(); if (!getMetaprovider().isFieldVisible(field)){ continue; } int currentFieldWidth = fieldWidthMap.get(field) + fieldOffset; long pctRemaining = 100 - pctTotalSoFar.longValue(); long pctWidth = (long)Math.ceil((currentFieldWidth * 100.0) / total.doubleValue() ); if (!fieldIterator.hasNext()){ pctWidth = Math.max(pctWidth, pctRemaining); //When no more fields max it out. } pctTotalSoFar.increment(pctWidth); Column column = columns.get(i); column.setProperty("width", pctWidth +"%"); i++; } } protected BitSet addHeadingsForLineLevelActions(Row header) { BitSet showAction = new BitSet(); int numActions = getSingleRecordActions().size(); for (int i = 0 ; i < numActions ; i ++ ){ Control action = header.createColumn(); action.setProperty("width", "1%"); showAction.clear(i); } return showAction; } protected void addLineLevelActions(Row row, M record, BitSet showAction) { Timer timer = cat.startTimer("Adding Line Level Actions"); try { _addLineLevelActions(row, record, showAction); }finally { timer.stop(); } } protected List<Method> getSingleRecordActions(){ return modelAwareness.getSingleRecordActions(); } protected void _addLineLevelActions(Row row, M record, BitSet showAction) { List<Method> singleRecordActions = getSingleRecordActions(); for (int actionIndex = 0 ; actionIndex < singleRecordActions.size() ; actionIndex ++ ){ Method m = singleRecordActions.get(actionIndex); Column actionLinkCell = row.createColumn(); Link singleRecordActionLink = modelAwareness.createSingleRecordActionLink(m, record); if (singleRecordActionLink != null){ actionLinkCell.addControl(singleRecordActionLink); showAction.set(actionIndex); } } } protected void addFields(Row row, M record){ Timer addingFields = cat.startTimer("Adding Fields"); try { _addFields(row, record); }finally { addingFields.stop(); } } protected Control getControl(String controlName, String fieldName, M record){ ModelReflector<M> reflector = modelAwareness.getReflector(); Method getter = reflector.getFieldGetter(fieldName); TypeConverter<?> converter = Database.getJdbcTypeHelper(reflector.getPool()).getTypeRef(getter.getReturnType()).getTypeConverter(); Control control = null; if (InputStream.class.isAssignableFrom(getter.getReturnType())){ FileTextBox ftb = (FileTextBox)modelAwareness.getInputControl(controlName,fieldName, record,null); String contentName = reflector.getContentName(record, fieldName); if (reflector.getContentSize(record, fieldName) != 0){ Path linkPath = modelAwareness.getPath().createRelativePath("/view/"+record.getId()); if (linkPath.canAccessControllerAction()){ ftb.setStreamUrl(modelAwareness.getPath().controllerPath()+"/view/"+record.getId(),contentName); control = ftb.getStreamLink(); }else { control = new Label("***"); } }else { control = new Label("No Attachment"); } control.addClass(converter.getDisplayClassName()); }else { Object value = reflector.get(record,fieldName); String sValue = converter.toString(value); if (reflector.isFieldPassword(fieldName)){ sValue = sValue.replaceAll(".", "\\*"); } boolean fieldIsLongForTextBox = reflector.isFieldValueALongText(fieldName); if (fieldIsLongForTextBox){ //Probably html formating is better. convert hard enter to br sValue = sValue.replaceAll("(<br/>)?\n(<br/>)?", "<br/>"); } String parentDescription = modelAwareness.getParentDescription(getter, record) ; if (!ObjectUtil.isVoid(parentDescription)){ Object parentId = value; Class<? extends Model> parentModelClass = reflector.getReferredModelClass(reflector.getReferredModelGetterFor(getter)); String tableName = LowerCaseStringCache.instance().get(Database.getTable(parentModelClass).getTableName()); sValue = parentDescription; ModelReflector<? extends Model> parentModelReflector = ModelReflector.instance(parentModelClass); if (parentModelReflector.isFieldValueALongText(parentModelReflector.getDescriptionField())){ //Probably html formating is better. convert hard enter to br sValue = sValue.replaceAll("(<br/>)?\n(<br/>)?", "<br/>"); } _IPath parentTarget = modelAwareness.getPath().createRelativePath( modelAwareness.getPath().action() + ( ObjectUtil.isVoid(modelAwareness.getPath().parameter()) ? "" : "/" + modelAwareness.getPath().parameter() )+ "/" + tableName + "/show/" + String.valueOf(parentId) ); if (parentTarget.canAccessControllerAction()){ control = new Link(parentTarget.getTarget()); control.setText(sValue); }else { control = new Label(sValue); } }else { control = new Label(sValue); control.addClass(converter.getDisplayClassName()); } } control.setProperty("width", "100%"); return control; } protected void _addFields(Row row, M record){ int rowIndex = row.getParent().getContainedControls().size(); for (String fieldName : getIncludedFields()) { Control control = getControl(getModelAwareness().getReflector().getModelClass().getSimpleName() + "["+rowIndex+"]." + fieldName,fieldName, record); if (!control.isEnabled() && !control.isVisible()){ continue; } Column column = null; if (getMetaprovider().isFieldVisible(fieldName)){ column = row.createColumn(); Integer currentMaxFieldWidth = maxFieldWidth.get(fieldName); Integer currentFieldWidth = getDataLength(control); if (currentMaxFieldWidth == null || currentMaxFieldWidth < currentFieldWidth){ maxFieldWidth.put(fieldName,currentFieldWidth); } }else { column = row.getLastColumn(); control.setVisible(false); } column.addControl(control); } } protected int getDataLength(Control control){ return control.getText().length(); } protected void addRecordToTable(M record, BitSet showAction){ Timer timer = cat.startTimer("Adding one record to table"); try { _addRecordToTable(record, showAction); }finally { timer.stop(); } } protected void _addRecordToTable(M record, BitSet showAction){ User u = (User)Database.getInstance().getCurrentUser(); Timer recordAccessibility = cat.startTimer("Checking Record accessibility"); try { if (u != null && !record.isAccessibleBy(u,modelAwareness.getReflector().getModelClass())){ return; } }finally { recordAccessibility.stop(); } Timer checkingIndexActionAccessibility = cat.startTimer("Checking index action Accessibility"); try { if (record.getId() > 0 && !modelAwareness.getPath().canAccessControllerAction("index",String.valueOf(record.getId()))){ return; } }finally { checkingIndexActionAccessibility.stop(); } Row row = table.createRow(); addLineLevelActions(row, record, showAction); addFields(row, record); } }