/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package com.venky.swf.controller; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.StringTokenizer; import java.util.logging.Level; import javax.activation.MimetypesFileTypeMap; import javax.servlet.http.HttpServletRequest; import org.apache.lucene.queryParser.QueryParser; import org.apache.lucene.search.Query; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.json.simple.JSONObject; import com.venky.cache.Cache; import com.venky.core.collections.LowerCaseStringCache; import com.venky.core.log.TimerStatistics.Timer; import com.venky.core.string.StringUtil; import com.venky.core.util.ExceptionUtil; import com.venky.core.util.ObjectUtil; import com.venky.digest.Encryptor; import com.venky.swf.controller.annotations.Depends; import com.venky.swf.controller.annotations.RequireLogin; import com.venky.swf.controller.annotations.SingleRecordAction; import com.venky.swf.db.Database; import com.venky.swf.db.JdbcTypeHelper.TypeConverter; import com.venky.swf.db.annotations.column.pm.PARTICIPANT; import com.venky.swf.db.annotations.column.ui.OnLookupSelect; import com.venky.swf.db.annotations.column.ui.OnLookupSelectionProcessor; import com.venky.swf.db.annotations.column.ui.mimes.MimeType; import com.venky.swf.db.model.Model; import com.venky.swf.db.model.reflection.ModelReflector; import com.venky.swf.db.table.Record; import com.venky.swf.db.table.Table; import com.venky.swf.exceptions.AccessDeniedException; import com.venky.swf.exceptions.MultiException; import com.venky.swf.integration.FormatHelper; import com.venky.swf.integration.IntegrationAdaptor; import com.venky.swf.path.Path; import com.venky.swf.path.Path.ControllerInfo; import com.venky.swf.plugins.lucene.index.LuceneIndexer; import com.venky.swf.routing.Config; import com.venky.swf.sql.Conjunction; import com.venky.swf.sql.Expression; import com.venky.swf.sql.Operator; import com.venky.swf.sql.Select; import com.venky.swf.views.BytesView; import com.venky.swf.views.ForwardedView; import com.venky.swf.views.HtmlView; import com.venky.swf.views.HtmlView.StatusType; import com.venky.swf.views.RedirectorView; import com.venky.swf.views.View; import com.venky.swf.views.model.ModelEditView; import com.venky.swf.views.model.ModelListView; import com.venky.swf.views.model.ModelShowView; /** * * @author venky */ public class ModelController<M extends Model> extends Controller { private Class<M> modelClass; private ModelReflector<M> reflector ; private boolean indexedModel = false; private IntegrationAdaptor<M, ?> integrationAdaptor = null; public ModelController(Path path) { super(path); modelClass = getPath().getModelClass(); reflector = ModelReflector.instance(modelClass); indexedModel = !reflector.getIndexedFieldGetters().isEmpty(); if (path.getProtocol() != MimeType.TEXT_HTML){ integrationAdaptor = IntegrationAdaptor.instance(modelClass, FormatHelper.getFormatClass(path.getProtocol())); } } public IntegrationAdaptor<M, ?> getIntegrationAdaptor() { return this.integrationAdaptor; } protected ModelReflector<M> getReflector(){ return reflector; } public View exportxls(){ ensureUI(); Workbook wb = new HSSFWorkbook(); super.exportxls(getModelClass(), wb); ByteArrayOutputStream os = new ByteArrayOutputStream(); try { wb.write(os); } catch (IOException e) { throw new RuntimeException(e); } return new BytesView(getPath(), os.toByteArray(),MimeType.APPLICATION_XLS,"content-disposition", "attachment; filename=" + getModelClass().getSimpleName() + ".xls"); } @Depends("save") @Override public View importxls(){ ensureUI(); return super.importxls(); } protected void ensureUI(){ if (integrationAdaptor != null) { throw new RuntimeException("Action is only available from UI"); } } @Override @RequireLogin(true) public View index() { Timer index = Config.instance().getLogger(getClass().getName()).startTimer(getReflector().getTableName() + ".index"); try { if (indexedModel){ return search(); }else { return list(); } }finally { index.stop(); } } public int getMaxListRecords(){ return MAX_LIST_RECORDS; } public View search(){ Map<String,Object> formData = new HashMap<String, Object>(); formData.putAll(getFormFields()); String q = ""; int maxRecords = getMaxListRecords(); if (!formData.isEmpty()){ rewriteQuery(formData); q = StringUtil.valueOf(formData.get("q")); Object mr = formData.get("maxRecords"); if (!ObjectUtil.isVoid(mr)){ maxRecords = Integer.parseInt(StringUtil.valueOf(mr)); } } return search(q,maxRecords); } public View search(String strQuery) { getFormFields().put("q",strQuery); Map<String,Object> formData = new HashMap<String, Object>(getFormFields()); rewriteQuery(formData); String q = StringUtil.valueOf(formData.get("q")); return search(q,getMaxListRecords()); } protected View search(String strQuery,int maxRecords) { if (!ObjectUtil.isVoid(strQuery)){ if (!getFormFields().containsKey("q")){ getFormFields().put("q", strQuery); } LuceneIndexer indexer = LuceneIndexer.instance(getModelClass()); Query q = indexer.constructQuery(strQuery); List<Integer> ids = indexer.findIds(q, Select.MAX_RECORDS_ALL_RECORDS); if (!ids.isEmpty()) { Select sel = new Select().from(getModelClass()).where(new Expression(getReflector().getPool(),Conjunction.AND) .add(new Expression(getReflector().getPool(),"ID",Operator.IN,ids.toArray())) .add(getPath().getWhereClause())).orderBy(getReflector().getOrderBy()); List<M> records = sel.execute(getModelClass(),maxRecords,new DefaultModelFilter<M>(getModelClass())); return list(records,maxRecords == 0 || records.size() < maxRecords); }else { return list(new ArrayList<M>(),true); } } return list(maxRecords); } protected void rewriteQuery(Map<String,Object> formData){ String strQuery = StringUtil.valueOf(formData.get("q")); StringBuilder q = new StringBuilder(); if (!ObjectUtil.isVoid(strQuery) && !strQuery.contains(":")){ for (String f:getReflector().getIndexedFields()){ if (q.length() > 0 ){ q.append(" OR "); } q.append("("); Method referredModelIdGetter = getReflector().getFieldGetter(f); for (StringTokenizer tk = new StringTokenizer(strQuery); tk.hasMoreTokens() ; ){ if (getReflector().getReferredModelGetterFor(referredModelIdGetter) != null){ q.append(f.substring(0,f.length()-"_ID".length())).append(":").append(QueryParser.escape(tk.nextToken())).append("*"); }else { q.append(f).append(":").append(QueryParser.escape(tk.nextToken())).append("*"); } if (tk.hasMoreTokens()){ q.append(" AND "); } } q.append(")"); } try { Integer.valueOf(strQuery); if (q.length() > 0){ q.append(" OR "); } q.append("("); q.append("ID:").append(strQuery); q.append(")"); }catch (NumberFormatException ex){ // Nothing to do. } formData.put("q", q.toString()); } Config.instance().getLogger(getClass().getName()).fine(formData.toString()); } public View list(){ return list(Select.MAX_RECORDS_ALL_RECORDS); } private View list(int maxRecords) { List<M> records = null; if (!reflector.isVirtual()) { Select q = new Select().from(modelClass); records = q.where(getPath().getWhereClause()).orderBy(getReflector().getOrderBy()).execute(modelClass, maxRecords ,new DefaultModelFilter<M>(getModelClass())); }else { records = getChildrenFromParent(); Expression where = getPath().getWhereClause(); Iterator<M> i = records.iterator(); while(i.hasNext()){ M record = i.next(); if (!where.eval(record)){ i.remove(); } } } return list(records, maxRecords == 0 || records.size() < maxRecords); } private List<M> getChildrenFromParent(){ List<M> children = new ArrayList<M>(); Class<? extends Model> parentClass = null; Method childGetter = null; for (Method rmg : reflector.getReferredModelGetters()){ Class<? extends Model> referredModelClass = reflector.getReferredModelClass(rmg); ModelReflector<? extends Model> ref = ModelReflector.instance(referredModelClass); if (ref.isVirtual()){ continue; } for (Method cg : ref.getChildGetters()){ if (ref.getChildModelClass(cg).isAssignableFrom(reflector.getModelClass())){ parentClass = referredModelClass; childGetter = cg; break; } } } List<ControllerInfo> controllerElements = new ArrayList<ControllerInfo>(getPath().getControllerElements()); Collections.reverse(controllerElements); Iterator<ControllerInfo> cInfoIter = controllerElements.iterator() ; if (cInfoIter.hasNext()){ cInfoIter.next();// The last model was self. } while(cInfoIter.hasNext()){ ControllerInfo info = cInfoIter.next(); if(info.getModelClass().isAssignableFrom(parentClass)){ Integer id = info.getId(); if (id == null){ continue; } Model parent = Database.getTable(info.getModelClass()).get(info.getId()); try { children = (List<M>) childGetter.invoke(parent); } catch (Exception e) { // } } } return children; } protected View list(List<M> records,boolean isCompleteList){ View v = null; if (integrationAdaptor != null){ v = integrationAdaptor.createResponse(getPath(),records); }else { View lv = constructModelListView(records,isCompleteList); if (lv instanceof HtmlView){ v = dashboard((HtmlView)lv); }else { // To support View Redirection.!! v = lv; } } return v; } protected View constructModelListView(List<M> records, boolean isCompleteList){ return new ModelListView<M>(getPath(), getIncludedFields(), records, isCompleteList); } protected String[] getIncludedFields(){ return null; } protected Class<M> getModelClass() { return modelClass; } @SingleRecordAction(icon="glyphicon-eye-open",tooltip="See this record") @Depends("index") public View show(int id) { M record = Database.getTable(modelClass).get(id); if (!record.isAccessibleBy(getSessionUser(),modelClass)){ throw new AccessDeniedException(); } return show(record); } protected View show(M record){ View view = null ; if (integrationAdaptor != null){ view = integrationAdaptor.createResponse(getPath(),record); }else { view = dashboard(createModelShowView(record)); } return view; } protected ModelShowView<M> createModelShowView(M record){ return constructModelShowView(getPath(),record); } protected ModelShowView<M> constructModelShowView(Path path, M record){ return new ModelShowView<M>(path, getIncludedFields(), record); } @Depends("index") public View view(int id){ return view(id,null); } private boolean isViewableInLine(String mimeType){ if (mimeType == null){ return false ; } if (mimeType.startsWith("image")){ return true; }else if (mimeType.startsWith("video")){ return true; }else if (mimeType.equals("audio/ogg")){ return true; } return false; } private View view(int id,Boolean asAttachment){ M record = Database.getTable(modelClass).get(id); if (record.isAccessibleBy(getSessionUser(),modelClass)){ try { for (Method getter : reflector.getFieldGetters()){ if (InputStream.class.isAssignableFrom(getter.getReturnType())){ String fieldName = reflector.getFieldName(getter); String fileName = reflector.getContentName(record,fieldName); String mimeType = reflector.getContentType(record, fieldName); if (reflector.getDefaultContentType().equals(mimeType) && fileName != null) { mimeType = MimetypesFileTypeMap.getDefaultFileTypeMap().getContentType(fileName); } if (asAttachment == null){ asAttachment = !isViewableInLine(mimeType); } if (fileName != null && asAttachment){ return new BytesView(getPath(), StringUtil.readBytes((InputStream)getter.invoke(record)), mimeType, "content-disposition","attachment; filename=\"" + fileName +"\""); }else { return new BytesView(getPath(), StringUtil.readBytes((InputStream)getter.invoke(record)), mimeType); } } } } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e.getCause()); } }else { throw new AccessDeniedException(); } return getSuccessView(); } @SingleRecordAction(icon="glyphicon-edit") @Depends("save,index") public View edit(int id) { ensureUI(); return dashboard(createModelEditView(id, "save")); } protected ModelEditView<M> createModelEditView(int id, String formAction){ M record = Database.getTable(modelClass).get(id); return createModelEditView(record, formAction); } protected ModelEditView<M> createModelEditView(M record, String formAction){ return createModelEditView(getPath(), record, formAction); } protected ModelEditView<M> createModelEditView(Path path, M record, String formAction){ if (record.isAccessibleBy(getSessionUser(),getModelClass())){ return constructModelEditView(path, record, formAction); }else { throw new AccessDeniedException(); } } protected ModelEditView<M> constructModelEditView(Path path, M record, String formAction){ return new ModelEditView<M>(path, getIncludedFields(), record,formAction); } @SingleRecordAction(icon="glyphicon-duplicate",tooltip="Duplicate") @Depends("save,index") public View clone(int id){ M record = Database.getTable(modelClass).get(id); M newrecord = clone(record); return blank(newrecord); } public M clone(M record){ Table<M> table = Database.getTable(modelClass); M newrecord = table.newRecord(); Record oldRaw = record.getRawRecord(); Record newRaw = newrecord.getRawRecord(); for (String f:oldRaw.getFieldNames()){ //Fields in raw records are column names. if (getReflector().isFieldCopiedWhileCloning(getReflector().getFieldName(f))){ newRaw.put(f, oldRaw.get(f)); } } newRaw.setNewRecord(true); return newrecord; } @Depends("save") public View blank(){ M record = Database.getTable(modelClass).newRecord(); return blank(record); } protected View blank(M record) { record.defaultFields(); getPath().fillDefaultsForReferenceFields(record,getModelClass()); record.setCreatorUserId(getSessionUser().getId()); record.setUpdaterUserId(getSessionUser().getId()); if (integrationAdaptor != null){ return integrationAdaptor.createResponse(getPath(),record); }else { return dashboard(createBlankView(record,"save")); } } protected ModelEditView<M> createBlankView(M record,String formAction){ return createBlankView(getPath(), record, formAction); } protected ModelEditView<M> createBlankView(Path path , M record, String formAction){ ModelEditView<M> mev = constructModelEditView(path, record,formAction); for (String field : reflector.getFields()){ if (reflector.isHouseKeepingField(field)){ mev.getIncludedFields().remove(field); } } return mev; } public View truncate(){ Select q = new Select().from(modelClass); List<M> records = q.where(getPath().getWhereClause()).execute(modelClass,new Select.AccessibilityFilter<M>()); for (M record: records){ if (getPath().canAccessControllerAction("destroy", String.valueOf(record.getId()))){ record.destroy(); }else { throw new AccessDeniedException("Don't have permission to destroy record " + record.getId()); } } return back(); } @SingleRecordAction(icon="glyphicon-trash") @Depends("index") public View destroy(int id){ M record = Database.getTable(modelClass).get(id); destroy(record); return getSuccessView(); } private void destroy(M record){ if (record != null){ if (record.isAccessibleBy(getSessionUser(),modelClass)){ record.destroy(); }else { throw new AccessDeniedException(); } } } protected View getSuccessView(){ View ret = null ; if (integrationAdaptor != null){ ret = integrationAdaptor.createStatusResponse(getPath(),null); }else { ret = back(); } return ret; } protected RedirectorView redirectTo(String action){ RedirectorView v = new RedirectorView(getPath(),action); return v; } protected View forwardTo(String action){ return new ForwardedView(getPath(), action); } public static interface Action<M> { public View noAction(M m); public void act(M m); public <C extends Model> void actOnChild(M parent,Class<C> childModelClass, Model c); public View error(M m); } public class SaveAction implements Action<M>{ public View noAction(M m){ return noActionView(m); } @Override public void act(M m) { save(m, getModelClass()); } @SuppressWarnings("unchecked") public <C extends Model> void actOnChild(M parent, Class<C> childModelClass, Model c){ ModelReflector<C> childReflector = ModelReflector.instance(childModelClass); for (String f : childReflector.getReferenceFields(getModelClass())){ if (childReflector.isFieldSettable(f)){ Object oldValue = childReflector.get(c, f); if (Database.getJdbcTypeHelper(childReflector.getPool()).isVoid(oldValue)){ childReflector.set(c, f, parent.getId()); } } } save((C)c,childModelClass); } @Override public View error(M m) { ModelEditView<M> errorView = null; if (m.getRawRecord().isNewRecord()){ errorView = createBlankView(getPath().createRelativePath("blank"),m,"save"); }else { errorView = createModelEditView(getPath().createRelativePath("edit/" + m.getId()), m,"save"); } return errorView; } } protected View saveModelFromForm(){ return performPostAction(getSaveAction()); } protected Action<M> getSaveAction(){ return new SaveAction(); } protected View performPostAction(Action<M> action){ Map<String,Object> formFields = getFormFields(); String id = (String)formFields.get("ID"); String lockId = (String)formFields.get("LOCK_ID"); M record = null; if (ObjectUtil.isVoid(id)) { record = Database.getTable(modelClass).newRecord(); } else { record = Database.getTable(modelClass).get(Integer.valueOf(id)); if (!ObjectUtil.isVoid(lockId)) { if (record.getLockId() != Long.parseLong(lockId)) { throw new RuntimeException("Stale record update prevented. Please reload and retry!"); } } if (!record.isAccessibleBy(getSessionUser(),modelClass)){ throw new AccessDeniedException(); } } List<String> setableFields = reflector.getRealFields(); for (String virtualField: reflector.getVirtualFields()){ if (reflector.isFieldSettable(virtualField)){ setableFields.add(virtualField); } } Iterator<String> e = formFields.keySet().iterator(); String buttonName = null; String digest = null; MultiException dataValidationExceptions = new MultiException("Invalid input: "); boolean hasUserModifiedData = false; while (e.hasNext()) { String name = e.next(); String fieldName = setableFields.contains(name) && !reflector.isHouseKeepingField(name) ? name : null; if (fieldName != null){ try { validateEnteredData(reflector,record,fieldName, formFields); }catch (Exception ex){ dataValidationExceptions.add(ex); hasUserModifiedData = true; } Object value = formFields.get(fieldName); Class<?> fieldClass = reflector.getFieldGetter(fieldName).getReturnType(); if (value == null && (Reader.class.isAssignableFrom(fieldClass) || InputStream.class.isAssignableFrom(fieldClass))){ continue; } reflector.set(record, fieldName, value); }else if ( name.startsWith("_SUBMIT")){ buttonName = name; }else if ( name.startsWith("_FORM_DIGEST")){ digest = (String)formFields.get("_FORM_DIGEST"); } } boolean isNew = record.getRawRecord().isNewRecord(); hasUserModifiedData = hasUserModifiedData || hasUserModifiedData(formFields,digest); if (hasUserModifiedData || isNew){ try { if (!dataValidationExceptions.isEmpty()){ throw dataValidationExceptions; } action.act(record); for (Class<? extends Model> childModelClass: reflector.getChildModels(true, false)){ @SuppressWarnings("unchecked") Map<Integer,Map<String,Object>> childFormRecords = (Map<Integer, Map<String, Object>>) formFields.get(childModelClass.getSimpleName()); if (childFormRecords != null){ for (Integer key: childFormRecords.keySet()){ Map<String,Object> childFormFields = childFormRecords.get(key); action.actOnChild(record,childModelClass, loadChildFromFormFields(childModelClass,childFormFields)); } } } if (isNew && hasUserModifiedData && buttonName.equals("_SUBMIT_MORE") && getPath().canAccessControllerAction("blank",String.valueOf(record.getId()))){ //Usability Logic: If user is not modifying data shown, then why be in data entry mode. getPath().addInfoMessage(getModelClass().getSimpleName() + " created sucessfully, press Done when finished."); return clone(record.getId()); } }catch (RuntimeException ex){ if (hasUserModifiedData){ Throwable th = ExceptionUtil.getRootCause(ex); Config.instance().printStackTrace(getClass(), th); String message = th.getMessage(); if (message == null){ message = th.toString(); } Database.getInstance().getCurrentTransaction().rollback(th); getPath().addMessage(StatusType.ERROR, message); View eView = action.error(record); if (eView instanceof HtmlView){ return dashboard((HtmlView)eView); }else { return eView; } } } return afterPersistDBView(record); }else { View view = action.noAction(record); if (view instanceof HtmlView){ return dashboard((HtmlView)view); }else { return view; } } } private <T extends Model> T loadChildFromFormFields( Class<T> childModelClass, Map<String, Object> formFields) { String id = (String)formFields.get("ID"); String lockId = (String)formFields.get("LOCK_ID"); T record = null; if (ObjectUtil.isVoid(id)) { record = Database.getTable(childModelClass).newRecord(); } else { record = Database.getTable(childModelClass).get(Integer.valueOf(id)); if (!ObjectUtil.isVoid(lockId)) { if (record.getLockId() != Long.parseLong(lockId)) { throw new RuntimeException("Stale record update prevented. Please reload and retry!"); } } if (!record.isAccessibleBy(getSessionUser(),childModelClass)){ throw new AccessDeniedException(); } } ModelReflector<T> childReflector = ModelReflector.instance(childModelClass); List<String> setableFields = childReflector.getRealFields(); for (String virtualField: childReflector.getVirtualFields()){ if (childReflector.isFieldSettable(virtualField)){ setableFields.add(virtualField); } } Iterator<String> e = formFields.keySet().iterator(); MultiException dataValidationExceptions = new MultiException("Invalid input: "); while (e.hasNext()) { String name = e.next(); String fieldName = setableFields.contains(name) && !childReflector.isHouseKeepingField(name) ? name : null; if (fieldName != null){ try { validateEnteredData(childReflector,record,fieldName, formFields); }catch (Exception ex){ dataValidationExceptions.add(ex); } Object value = formFields.get(fieldName); Class<?> fieldClass = childReflector.getFieldGetter(fieldName).getReturnType(); if (value == null && (Reader.class.isAssignableFrom(fieldClass) || InputStream.class.isAssignableFrom(fieldClass))){ continue; } childReflector.set(record, fieldName, value); } } if (!dataValidationExceptions.isEmpty()){ throw dataValidationExceptions; } return record; } protected View defaultActionView(M record){ View v = null; if (integrationAdaptor != null){ v = integrationAdaptor.createResponse(getPath(),record); }else{ v = back(); } return v; } protected View noActionView(M record){ return defaultActionView(record); } protected View afterPersistDBView(M record){ return defaultActionView(record); } private static void computeHash(StringBuilder hash, ModelReflector<? extends Model> reflector, Map<String,Object> formFields, String fieldPrefix){ for (String field: reflector.getFields()){ if (!formFields.containsKey(field) || reflector.isFieldDisabled(field) ){ continue; } Object currentValue = formFields.get(field); if (hash.length() > 0){ hash.append(","); } if (fieldPrefix != null){ hash.append(fieldPrefix); } hash.append(field).append("=").append(StringUtil.valueOf(currentValue)); String autoCompleteHelperField = "_AUTO_COMPLETE_"+field; if (formFields.containsKey(autoCompleteHelperField)){ hash.append(","); String autoCompleteHelperFieldValue = StringUtil.valueOf(formFields.get(autoCompleteHelperField)); if (fieldPrefix != null){ hash.append(fieldPrefix); } hash.append(autoCompleteHelperField).append("=").append(autoCompleteHelperFieldValue); } } for (Class<? extends Model> modelClass: reflector.getChildModels(true, false)){ String modelName = modelClass.getSimpleName(); ModelReflector<? extends Model> childModelReflector = ModelReflector.instance(modelClass); @SuppressWarnings("unchecked") SortedMap<Integer,Map<String,Object>> records = (SortedMap<Integer, Map<String, Object>>) formFields.get(modelName); if (records == null){ continue; } for (Integer key: records.keySet()){ Map<String,Object> childFormFields = records.get(key); computeHash(hash, childModelReflector, childFormFields,modelClass.getSimpleName()+"[" + key + "]."); } } } protected boolean hasUserModifiedData(Map<String,Object> formFields, String oldDigest){ StringBuilder hash = new StringBuilder(); computeHash(hash, reflector, formFields,null); String newDigest = hash == null ? null : Encryptor.encrypt(hash.toString()); return !ObjectUtil.equals(newDigest, oldDigest); } private <T extends Model> void validateEnteredData(ModelReflector<T> reflector,T record, String field, Map<String,Object> formFields){ String autoCompleteHelperField = "_AUTO_COMPLETE_"+field; if (!formFields.containsKey(autoCompleteHelperField)){ return ; } if (!reflector.isFieldEditable(field)){ return; } Object autoCompleteHelperFieldValue = formFields.get(autoCompleteHelperField); Object currentValue = Database.getJdbcTypeHelper(reflector.getPool()).getTypeRef(Integer.class).getTypeConverter().valueOf(formFields.get(field)); Method referredModelIdGetter = reflector.getFieldGetter(field); Method referredModelGetter = referredModelIdGetter == null ? null : reflector.getReferredModelGetterFor(referredModelIdGetter); Class<? extends Model> referredModelClass = referredModelGetter == null ? null : reflector.getReferredModelClass(referredModelGetter); if (referredModelClass == null){ return ;//Defensive. not really needed due to first condition. of check existance of autoCompleteHelperField in formFields. } ModelReflector<? extends Model> referredModelReflector = ModelReflector.instance(referredModelClass); String descriptionField = referredModelReflector.getDescriptionField(); Method descriptionFieldGetter = referredModelReflector.getFieldGetter(descriptionField); Model referredModel = null; String fieldLiteral = referredModelGetter.getName().substring("get".length()); if (Database.getJdbcTypeHelper(referredModelReflector.getPool()).isVoid(autoCompleteHelperFieldValue)) { if (!Database.getJdbcTypeHelper(referredModelReflector.getPool()).isVoid(currentValue)){ formFields.put(field, ""); } return; } //In some tables that don't have description column or have non string description columns, this would have caused an issue of class cast in db.. autoCompleteHelperFieldValue = Database.getJdbcTypeHelper(referredModelReflector.getPool()).getTypeRef(descriptionFieldGetter.getReturnType()).getTypeConverter().valueOf(autoCompleteHelperFieldValue); //autoCompleteHelperFieldValue is not void. Expression where = new Expression(referredModelReflector.getPool(),Conjunction.AND); where.add(getAutoCompleteBaseWhere(reflector, record, field)); where.add(new Expression(referredModelReflector.getPool(),referredModelReflector.getColumnDescriptor(descriptionField).getName(),Operator.EQ, autoCompleteHelperFieldValue)); @SuppressWarnings({ "rawtypes", "unchecked" }) List<? extends Model> models = new Select().from(referredModelReflector.getRealModelClass()).where(where).execute(referredModelClass,new Select.AccessibilityFilter()); if (models.size() == 1){ referredModel = models.get(0); currentValue = StringUtil.valueOf(referredModel.getId()); formFields.put(field, currentValue); } if (referredModel == null){ throw new RuntimeException("Please choose " + fieldLiteral + " from lookup." ); }else { Object descriptionValue = referredModelReflector.get(referredModel, descriptionField); String sDescriptionValue = Database.getJdbcTypeHelper(referredModelReflector.getPool()).getTypeRef(descriptionFieldGetter.getReturnType()).getTypeConverter().toString(descriptionValue); String sAutoCompleteFieldDesc = Database.getJdbcTypeHelper(referredModelReflector.getPool()).getTypeRef(descriptionFieldGetter.getReturnType()).getTypeConverter().toString(autoCompleteHelperFieldValue); if (!ObjectUtil.equals(sDescriptionValue, sAutoCompleteFieldDesc)){ throw new RuntimeException("Please choose " + fieldLiteral + " from lookup." ); } } } public View save() { HttpServletRequest request = getPath().getRequest(); if (!request.getMethod().equalsIgnoreCase("POST")) { throw new RuntimeException("Cannot call save in any other method other than POST"); } if (integrationAdaptor != null){ return saveModelsFromRequest(); }else { return saveModelFromForm(); } } private <T> View saveModelsFromRequest(){ List<M> models = persistModelsFromRequest(); return integrationAdaptor.createResponse(getPath(),models); } protected <T> List<M> persistModelsFromRequest(){ List<M> models = integrationAdaptor.readRequest(getPath()); for (M m: models){ try { save(m, getModelClass()); }catch(Exception ex){ Database.getInstance().getCache(getReflector()).clear(); if (ex instanceof RuntimeException){ throw (RuntimeException)ex; }else { throw new RuntimeException(ex); } } } return models; } @SuppressWarnings("unchecked") public View onAutoCompleteSelect(){ ensureUI(); List<String> fields = reflector.getFields(); Map<String,Object> formData = getFormFields(); M model = null; if (formData.containsKey("ID")){ model = Database.getTable(modelClass).get(Integer.valueOf(formData.get("ID").toString())); model = model.cloneProxy(); }else { model = Database.getTable(modelClass).newRecord(); } String autoCompleteFieldName = null; for (String fieldName : formData.keySet()){ if (fields.contains(fieldName)){ Object ov = formData.get(fieldName); if (reflector.isFieldSettable(fieldName)){ reflector.set(model,fieldName, ov); } }else if (fieldName.startsWith("_AUTO_COMPLETE_")){ autoCompleteFieldName = fieldName.split("_AUTO_COMPLETE_")[1]; } } Method autoCompleteFieldGetter = reflector.getFieldGetter(autoCompleteFieldName); OnLookupSelect onlookup = reflector.getAnnotation(autoCompleteFieldGetter, OnLookupSelect.class); if (onlookup != null){ try { OnLookupSelectionProcessor<M> processor = (OnLookupSelectionProcessor<M>) Class.forName(onlookup.processor()).newInstance(); processor.process(autoCompleteFieldName, model); } catch (Exception e) { // } } TypeConverter<Integer> integerTypeConverter = (TypeConverter<Integer>) Database.getJdbcTypeHelper(reflector.getPool()).getTypeRef(Integer.class).getTypeConverter(); JSONObject obj = new JSONObject(); Record record = model.getRawRecord(); for (String f:record.getDirtyFields()){ Object value = record.get(f); obj.put(f,value); Method fieldGetter = reflector.getFieldGetter(f); Method referredModelGetter = reflector.getReferredModelGetterFor(fieldGetter); if (referredModelGetter != null){ Class<? extends Model> referredModelClass = reflector.getReferredModelClass(referredModelGetter); ModelReflector<? extends Model> referredModelReflector = ModelReflector.instance(referredModelClass); int referredModelId = integerTypeConverter.valueOf(record.get(f)); Model referredModel = Database.getTable(referredModelClass).get(referredModelId); String referredModelDescriptionField = referredModelReflector.getDescriptionField(); obj.put("_AUTO_COMPLETE_"+f, referredModelReflector.get(referredModel, referredModelDescriptionField)); } } return new BytesView(getPath(), obj.toString().getBytes()); } public View autocomplete() { ensureUI(); List<String> fields = reflector.getFields(); Map<String,Object> formData = getFormFields(); M model = null; if (formData.containsKey("ID")){ model = Database.getTable(modelClass).get(Integer.valueOf(formData.get("ID").toString())); }else { model = Database.getTable(modelClass).newRecord(); } model = model.cloneProxy(); String autoCompleteFieldName = null; String value = ""; for (String fieldName : formData.keySet()){ if (fields.contains(fieldName)){ Object ov = formData.get(fieldName); if (reflector.isFieldSettable(fieldName)){ reflector.set(model,fieldName, ov); } }else if (fieldName.startsWith("_AUTO_COMPLETE_")){ autoCompleteFieldName = fieldName.split("_AUTO_COMPLETE_")[1]; value = StringUtil.valueOf(formData.get(fieldName)); } } Config.instance().getLogger(getClass().getName()).info(autoCompleteFieldName + "=" + value); model.getRawRecord().remove(autoCompleteFieldName); Expression where = getAutoCompleteBaseWhere(reflector,model, autoCompleteFieldName); ModelReflector<? extends Model> autoCompleteModelReflector = getAutoCompleteModelReflector(reflector,autoCompleteFieldName); return super.autocomplete(autoCompleteModelReflector.getModelClass(), where, autoCompleteModelReflector.getDescriptionField(), value); } private <T extends Model> Expression getAutoCompleteBaseWhere(ModelReflector<T> reflector, T model , String autoCompleteFieldName) { Expression where = new Expression(reflector.getPool(),Conjunction.AND); where.add(getAutoCompleteFieldParticipationWhere(reflector,model,autoCompleteFieldName)); Path autoCompletePath = getAutoCompleteModelPath(reflector,autoCompleteFieldName); where.add(autoCompletePath.getWhereClause()); return where; } private <T extends Model> Expression getAutoCompleteFieldParticipationWhere(ModelReflector<T> reflector, T model, String autoCompleteFieldName){ Expression where = new Expression(reflector.getPool(),Conjunction.AND); Method autoCompleteFieldGetter = reflector.getFieldGetter(autoCompleteFieldName); PARTICIPANT participant = reflector.getAnnotation(autoCompleteFieldGetter, PARTICIPANT.class); if (participant != null){ Cache<String,Map<String,List<Integer>>> pOptions = getSessionUser().getParticipationOptions(reflector.getModelClass(),model); if (pOptions.get(participant.value()).containsKey(autoCompleteFieldName)){ List<Integer> autoCompleteFieldValues = pOptions.get(participant.value()).get(autoCompleteFieldName); if (!autoCompleteFieldValues.isEmpty()){ autoCompleteFieldValues.remove(null); // We need not try to use null for lookups. where.add(Expression.createExpression(reflector.getPool(),"ID",Operator.IN,autoCompleteFieldValues.toArray())); }else { where.add(new Expression(reflector.getPool(),"ID",Operator.EQ)); } } } return where; } private <T extends Model> ModelReflector<? extends Model> getAutoCompleteModelReflector(ModelReflector<T> reflector, String autoCompleteFieldName){ Method autoCompleteFieldGetter = reflector.getFieldGetter(autoCompleteFieldName); return getAutoCompleteModelReflector(reflector, autoCompleteFieldGetter); } private <T extends Model> ModelReflector<? extends Model> getAutoCompleteModelReflector(ModelReflector<T> reflector,Method autoCompleteFieldGetter){ Class<? extends Model> autoCompleteModelClass = reflector.getReferredModelClass(reflector.getReferredModelGetterFor(autoCompleteFieldGetter)); ModelReflector<? extends Model> autoCompleteModelReflector = ModelReflector.instance(autoCompleteModelClass); return autoCompleteModelReflector; } private <T extends Model> Path getAutoCompleteModelPath(ModelReflector<T> reflector,String autoCompleteFieldName){ return getAutoCompleteModelPath(getAutoCompleteModelReflector(reflector,autoCompleteFieldName)); } private Path getAutoCompleteModelPath(ModelReflector<? extends Model> autoCompleteModelReflector){ Path referredModelPath = getPath().createRelativePath( (getPath().parameter() != null? "" : getPath().action()) +"/" + LowerCaseStringCache.instance().get(autoCompleteModelReflector.getTableName()) + "/index"); return referredModelPath; } @Override protected ImportSheetFilter getDefaultImportSheetFilter(){ return new ImportSheetFilter() { @Override public boolean filter(Sheet sheet) { return sheet.getSheetName().equals(StringUtil.pluralize(getModelClass().getSimpleName())); } }; } public View detail(){ if (!getPath().getRequest().getMethod().equalsIgnoreCase("POST")) { throw new RuntimeException("Api only supports POST method"); } IntegrationAdaptor<M, ?> integrationAdaptor = getIntegrationAdaptor(); if (integrationAdaptor != null) { List<M> models = integrationAdaptor.readRequest(getPath()); for (Iterator<M> i = models.iterator(); i.hasNext() ; ){ M m = i.next(); if (m.getRawRecord().isNewRecord()){ i.remove(); } } return integrationAdaptor.createResponse(getPath(),models); } throw new AccessDeniedException("Cannot call this api from ui"); } public View persist(){ if (!getPath().getRequest().getMethod().equalsIgnoreCase("POST")) { throw new RuntimeException("Api only supports POST method"); } IntegrationAdaptor<M, ?> integrationAdaptor = getIntegrationAdaptor(); if (integrationAdaptor != null) { List<M> models = persistModelsFromRequest(); Config.instance().getLogger(getReflector().getModelClass().getName()).log(Level.INFO,"Persisted {0} records." , models.size()); return integrationAdaptor.createStatusResponse(getPath(), null); } throw new AccessDeniedException("Cannot call this api from ui"); } }