/* =================================================================== * ListManagerForm.java * * Created Sep 7, 2005 7:25:20 AM * * Copyright (c) 2005 Matt Magoffin (spamsqr@msqr.us) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA * =================================================================== * $Id: ListManagerForm.java 62 2009-05-12 07:37:44Z msqr $ * =================================================================== */ package magoffin.matt.ieat.web.setup; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import magoffin.matt.ieat.biz.DomainObjectFactory; import magoffin.matt.ieat.biz.UserBiz; import magoffin.matt.ieat.dao.BaseDao; import magoffin.matt.ieat.dao.CourseDao; import magoffin.matt.ieat.dao.DifficultyDao; import magoffin.matt.ieat.dao.EthnicityDao; import magoffin.matt.ieat.dao.IngredientDao; import magoffin.matt.ieat.dao.PrepTimeDao; import magoffin.matt.ieat.dao.RecipeDao; import magoffin.matt.ieat.domain.Base; import magoffin.matt.ieat.domain.Course; import magoffin.matt.ieat.domain.Difficulty; import magoffin.matt.ieat.domain.Ethnicity; import magoffin.matt.ieat.domain.Ingredient; import magoffin.matt.ieat.domain.PrepTime; import magoffin.matt.ieat.web.WebUtil; import magoffin.matt.xweb.XwebParameter; import magoffin.matt.xweb.util.DynamicInitializer; import magoffin.matt.xweb.util.DynamicInitializerRequestDataBinder; import magoffin.matt.xweb.util.XwebParamDao; import org.apache.log4j.Logger; import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import org.springframework.util.StringUtils; import org.springframework.validation.BindException; import org.springframework.validation.Errors; import org.springframework.validation.Validator; import org.springframework.web.bind.ServletRequestDataBinder; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.SimpleFormController; /** * Form controller for managing list data. * * @author Matt Magoffin (spamsqr@msqr.us) * @version $Revision: 62 $ $Date: 2009-05-12 19:37:44 +1200 (Tue, 12 May 2009) $ */ public class ListManagerForm extends SimpleFormController { /** The original value prefix. */ public static final String ORIGINAL_VALUE_PREFIX = "_orig_"; /** A key for deletion. */ public static final String DELETE_KEY = "_delete_"; /** Domain class. */ public static class PropertyMetaData { private boolean primaryKey = false; private String name = null; private String displayName = null; /** Type of property: 1 = String, 2 = number, 3 = boolean */ private int type = 1; private int length = 0; /** * @return the displayName */ public String getDisplayName() { return displayName; } /** * @param displayName the displayName to set */ public void setDisplayName(String displayName) { this.displayName = displayName; } /** * @return the length */ public int getLength() { return length; } /** * @param length the length to set */ public void setLength(int length) { this.length = length; } /** * @return the name */ public String getName() { return name; } /** * @param name the name to set */ public void setName(String name) { this.name = name; } /** * @return the primaryKey */ public boolean isPrimaryKey() { return primaryKey; } /** * @param primaryKey the primaryKey to set */ public void setPrimaryKey(boolean primaryKey) { this.primaryKey = primaryKey; } /** * @return the type */ public int getType() { return type; } /** * @param type the type to set */ public void setType(int type) { this.type = type; } } /** Metadata about our list of beans. */ public static class ListMetaData { private PropertyMetaData[] properties; /** * @return the properties */ public PropertyMetaData[] getProperties() { return properties; } /** * @param properties the properties to set */ public void setProperties(PropertyMetaData[] properties) { this.properties = properties; } } /** The command object. */ public static class ListCommand { private Class beanClass = null; private Integer reassignPrimaryKey = null; private List> beans = new ArrayList>(); private Map newBean = new LinkedHashMap(); /** * @return the beanClass */ public Class getBeanClass() { return beanClass; } /** * @param beanClass the beanClass to set */ public void setBeanClass(Class beanClass) { this.beanClass = beanClass; } /** * @return the beans */ public List> getBeans() { return beans; } /** * @param beans the beans to set */ public void setBeans(List> beans) { this.beans = beans; } /** * @return the newBean */ public Map getNewBean() { return newBean; } /** * @param newBean the newBean to set */ public void setNewBean(Map newBean) { this.newBean = newBean; } /** * @return the reassignPrimaryKey */ public Integer getReassignPrimaryKey() { return reassignPrimaryKey; } /** * @param reassignPrimaryKey the reassignPrimaryKey to set */ public void setReassignPrimaryKey(Integer reassignPrimaryKey) { this.reassignPrimaryKey = reassignPrimaryKey; } } /** A dynamic initializer. */ public static class ListInitializer implements DynamicInitializer { /* (non-Javadoc) * @see magoffin.matt.ieat.util.DynamicInitializer#newInstance(java.lang.Object, java.lang.String) */ public Object newInstance(Object bean, String property) { return new LinkedHashMap(); } } /** A validator. */ public class ListValidator implements Validator { /* (non-Javadoc) * @see org.springframework.validation.Validator#supports(java.lang.Class) */ @SuppressWarnings("unchecked") public boolean supports(Class clazz) { return ListCommand.class.isAssignableFrom(clazz); } /* (non-Javadoc) * @see org.springframework.validation.Validator#validate(java.lang.Object, org.springframework.validation.Errors) */ public void validate(Object obj, Errors errors) { // verify that if any item marked for deletion, reassigned properly ListCommand listCmd = (ListCommand)obj; if ( listCmd.beans == null ) return; Set itemsToDelete = new LinkedHashSet(); PropertyMetaData primaryKeyProp = getPrimaryKeyProperty( meta.get(listCmd.beanClass.getName())); for ( Iterator> itr = listCmd.beans.iterator(); itr.hasNext(); ) { Map beanMap = itr.next(); if ( beanMap.containsKey(DELETE_KEY) ) { itemsToDelete.add(beanMap.get(primaryKeyProp.name)); } } if ( itemsToDelete.size() > 0 ) { if ( listCmd.reassignPrimaryKey == null ) { errors.reject("lists.reassign.required","Reassign item required."); } else if ( itemsToDelete.contains(listCmd.reassignPrimaryKey.toString()) ) { errors.reject("lists.reassign.illegal","Can't reassign to an item to delete."); } } } } private Map meta = null; // map of Class name to ListMetaData private RecipeDao recipeDao = null; private BaseDao baseDao = null; private CourseDao courseDao = null; private DifficultyDao difficultyDao = null; private EthnicityDao ethnicityDao = null; private IngredientDao ingredientDao = null; private PrepTimeDao prepTimeDao = null; private XwebParamDao parameterDao = null; private UserBiz userBiz = null; private DomainObjectFactory domainObjectFactory = null; private final Logger log = Logger.getLogger(getClass()); private static final Pattern MSG_FORMAT = Pattern.compile("\\W"); @Override protected void initApplicationContext() { setValidator(new ListValidator()); } @Override protected ServletRequestDataBinder createBinder(HttpServletRequest request, Object command) throws Exception { Map dynamicMap = new LinkedHashMap(); dynamicMap.put("beans",new ListInitializer()); DynamicInitializerRequestDataBinder binder = new DynamicInitializerRequestDataBinder(command,getCommandName(), dynamicMap); if (getMessageCodesResolver() != null) { binder.setMessageCodesResolver(getMessageCodesResolver()); } initBinder(request, binder); return binder; } @SuppressWarnings("unchecked") @Override protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws Exception { WebUtil.getAdminBizContext(request,userBiz); ListCommand listCmd = (ListCommand)command; ListMetaData lmd = meta.get(listCmd.beanClass.getName()); PropertyMetaData primaryKeyProp = getPrimaryKeyProperty(lmd); int updateCount = 0; // for any changed rows, update in DB for ( Iterator itr = listCmd.beans.iterator(); itr.hasNext(); ) { Map beanMap = (Map)itr.next(); boolean changed = false; String pkValue = (String)beanMap.get(primaryKeyProp.name); if ( beanMap.containsKey(DELETE_KEY) ) { deleteBean(beanMap,request.getLocale(),listCmd.beanClass, pkValue,listCmd.reassignPrimaryKey); updateCount++; continue; } BeanWrapper bean = getBean(listCmd.beanClass,pkValue); for ( Iterator beanMapItr = beanMap.keySet().iterator(); beanMapItr.hasNext(); ) { String propName = (String)beanMapItr.next(); if ( propName.startsWith("_") || propName.equals(primaryKeyProp.name)) { continue; // skip internal values and primary key } String propValue = (String)beanMap.get(propName); String propOrigValue = (String)beanMap.get(ORIGINAL_VALUE_PREFIX+propName); if ( !propValue.equals(propOrigValue) ) { changed = true; if ( log.isDebugEnabled() ) { log.debug("Changing property [" +propName +"] to [" +propValue +"] on bean [" +listCmd.beanClass.getName() +"]"); } bean.setPropertyValue(propName,propValue); } } if ( changed ) { saveBean(bean.getWrappedInstance(),beanMap,request.getLocale()); updateCount++; } } // see if a new bean has been added boolean hasNew = false; BeanWrapper bean = getBean(listCmd.beanClass,null); for ( int i = 0; i < lmd.properties.length; i++ ) { PropertyMetaData pmd = lmd.properties[i]; if ( pmd.primaryKey ) continue; String propValue = (String)listCmd.newBean.get(pmd.name); if ( StringUtils.hasText(propValue) ) { hasNew = true; } bean.setPropertyValue(pmd.name,propValue); } if ( hasNew ) { saveBean(bean.getWrappedInstance(),listCmd.newBean,request.getLocale()); updateCount++; } Map model = errors.getModel(); Map refData = referenceData(request,command,errors); model.putAll(refData); if ( updateCount > 0 ) { model.put("updateCount", updateCount); } return new ModelAndView(getSuccessView(), model); } private void deleteBean(Map beanMap, Locale locale, Class beanClass, String pkValue, Integer reassignPrimaryKey) { Integer pk = Integer.valueOf(pkValue); String removeSettingKey = null; if ( Base.class.isAssignableFrom(beanClass) ) { recipeDao.reassignBase(pk,reassignPrimaryKey); baseDao.delete(baseDao.get(pk)); String origValueKey = ORIGINAL_VALUE_PREFIX +"value"; if ( beanMap.containsKey(origValueKey) ) { removeSettingKey = "msg:base." +locale.getLanguage() +":" +MSG_FORMAT.matcher( (String)beanMap.get(origValueKey)).replaceAll(""); } } else if ( Course.class.isAssignableFrom(beanClass) ) { recipeDao.reassignCourse(pk,reassignPrimaryKey); courseDao.delete(courseDao.get(pk)); String origValueKey = ORIGINAL_VALUE_PREFIX +"value"; if ( beanMap.containsKey(origValueKey) ) { removeSettingKey = "msg:course." +locale.getLanguage() +":" +MSG_FORMAT.matcher( (String)beanMap.get(origValueKey)).replaceAll(""); } } else if ( Difficulty.class.isAssignableFrom(beanClass) ) { recipeDao.reassignDifficulty(pk,reassignPrimaryKey); difficultyDao.delete(difficultyDao.get(pk)); String origValueKey = ORIGINAL_VALUE_PREFIX +"value"; if ( beanMap.containsKey(origValueKey) ) { removeSettingKey = "msg:difficulty." +locale.getLanguage() +":" +MSG_FORMAT.matcher( (String)beanMap.get(origValueKey)).replaceAll(""); } } else if ( Ethnicity.class.isAssignableFrom(beanClass) ) { recipeDao.reassignEthnicity(pk,reassignPrimaryKey); ethnicityDao.delete(ethnicityDao.get(pk)); String origValueKey = ORIGINAL_VALUE_PREFIX +"value"; if ( beanMap.containsKey(origValueKey) ) { removeSettingKey = "msg:ethnicity." +locale.getLanguage() +":" +MSG_FORMAT.matcher( (String)beanMap.get(origValueKey)).replaceAll(""); } } else if ( Ingredient.class.isAssignableFrom(beanClass) ) { recipeDao.reassignIngredient(pk,reassignPrimaryKey); ingredientDao.delete(ingredientDao.get(pk)); String origValueKey = ORIGINAL_VALUE_PREFIX +"name"; if ( beanMap.containsKey(origValueKey) ) { removeSettingKey = "msg:ingredient." +locale.getLanguage() +":" +MSG_FORMAT.matcher( (String)beanMap.get(origValueKey)).replaceAll(""); } } else if ( PrepTime.class.isAssignableFrom(beanClass) ) { recipeDao.reassignPrepTime(pk,reassignPrimaryKey); prepTimeDao.delete(prepTimeDao.get(pk)); String origValueKey = ORIGINAL_VALUE_PREFIX +"value"; if ( beanMap.containsKey(origValueKey) ) { removeSettingKey = "msg:course." +locale.getLanguage() +":" +MSG_FORMAT.matcher( (String)beanMap.get(origValueKey)).replaceAll(""); } } if ( removeSettingKey != null ) { parameterDao.removeParameter(removeSettingKey); } } private void saveBean(Object bean, Map beanMap, Locale locale) { String settingKey = "msg:" +locale.getLanguage() +":"; String origSettingKey = "msg:" +locale.getLanguage() +":"; String settingValue = null; if ( bean instanceof Base ) { Base base = (Base)bean; baseDao.store(base); settingKey += "base." +MSG_FORMAT.matcher(base.getValue()).replaceAll(""); settingValue = base.getValue(); String origValueKey = ORIGINAL_VALUE_PREFIX +"value"; if ( beanMap.containsKey(origValueKey) ) { origSettingKey += "base." +MSG_FORMAT.matcher( (String)beanMap.get(origValueKey)).replaceAll(""); } } else if ( bean instanceof Course ) { Course course = (Course)bean; courseDao.store(course); settingKey += "course." +MSG_FORMAT.matcher(course.getValue()).replaceAll(""); settingValue = course.getValue(); String origValueKey = ORIGINAL_VALUE_PREFIX +"value"; if ( beanMap.containsKey(origValueKey) ) { origSettingKey += "course." +MSG_FORMAT.matcher( (String)beanMap.get(origValueKey)).replaceAll(""); } } else if ( bean instanceof Difficulty ) { Difficulty difficulty = (Difficulty)bean; difficultyDao.store(difficulty); settingKey += "difficulty." +MSG_FORMAT.matcher(difficulty.getValue()).replaceAll(""); settingValue = difficulty.getValue(); String origValueKey = ORIGINAL_VALUE_PREFIX +"value"; if ( beanMap.containsKey(origValueKey) ) { origSettingKey += "difficulty." +MSG_FORMAT.matcher( (String)beanMap.get(origValueKey)).replaceAll(""); } } else if ( bean instanceof Ethnicity ) { Ethnicity ethnicity = (Ethnicity)bean; ethnicityDao.store(ethnicity); settingKey += "ethnicity." +MSG_FORMAT.matcher(ethnicity.getValue()).replaceAll(""); settingValue = ethnicity.getValue(); String origValueKey = ORIGINAL_VALUE_PREFIX +"value"; if ( beanMap.containsKey(origValueKey) ) { origSettingKey += "ethnicity." +MSG_FORMAT.matcher( (String)beanMap.get(origValueKey)).replaceAll(""); } } else if ( bean instanceof Ingredient ) { Ingredient ingredient = (Ingredient)bean; ingredientDao.store(ingredient); settingKey += "ingredient." +MSG_FORMAT.matcher(ingredient.getName()).replaceAll(""); settingValue = ingredient.getName(); String origValueKey = ORIGINAL_VALUE_PREFIX +"name"; if ( beanMap.containsKey(origValueKey) ) { origSettingKey += "ingredient." +MSG_FORMAT.matcher( (String)beanMap.get(origValueKey)).replaceAll(""); } } else if ( bean instanceof PrepTime ) { PrepTime prepTime = (PrepTime)bean; prepTimeDao.store(prepTime); settingKey += "prepTime." +MSG_FORMAT.matcher(prepTime.getValue()).replaceAll(""); settingValue = prepTime.getValue(); String origValueKey = ORIGINAL_VALUE_PREFIX +"value"; if ( beanMap.containsKey(origValueKey) ) { origSettingKey += "prepTime." +MSG_FORMAT.matcher( (String)beanMap.get(origValueKey)).replaceAll(""); } } // remove old setting key (if changed) if ( !origSettingKey.equals(settingKey) ) { parameterDao.removeParameter(origSettingKey); } XwebParameter setting = this.domainObjectFactory.newXwebParameterInstance(); setting.setKey(settingKey); setting.setValue(settingValue); parameterDao.updateParameter(setting); } private BeanWrapper getBean(Class beanClass, String pkValue) { BeanWrapper result = null; if ( StringUtils.hasText(pkValue) ) { Integer pk = Integer.valueOf(pkValue); if ( Base.class.isAssignableFrom(beanClass) ) { result = new BeanWrapperImpl(baseDao.get(pk)); } else if ( Course.class.isAssignableFrom(beanClass) ) { result = new BeanWrapperImpl(courseDao.get(pk)); } else if ( Difficulty.class.isAssignableFrom(beanClass) ) { result = new BeanWrapperImpl(difficultyDao.get(pk)); } else if ( Ethnicity.class.isAssignableFrom(beanClass) ) { result = new BeanWrapperImpl(ethnicityDao.get(pk)); } else if ( Ingredient.class.isAssignableFrom(beanClass) ) { result = new BeanWrapperImpl(ingredientDao.get(pk)); } else if ( PrepTime.class.isAssignableFrom(beanClass)) { result = new BeanWrapperImpl(prepTimeDao.get(pk)); } } else { try { Object o = beanClass.newInstance(); result = new BeanWrapperImpl(o); } catch (Exception e) { throw new RuntimeException("Unable to instantiate bean",e); } } return result; } private PropertyMetaData getPrimaryKeyProperty(ListMetaData lmd) { for ( int i = 0; i < lmd.properties.length; i++ ) { if ( lmd.properties[i].primaryKey ) { return lmd.properties[i]; } } throw new IllegalArgumentException("No primary key found for ListMetaData"); } @SuppressWarnings("unchecked") @Override protected Map referenceData(HttpServletRequest request, Object command, Errors errors) throws Exception { WebUtil.getAdminBizContext(request,userBiz); ListCommand listCmd = (ListCommand)command; // pull data from backend DB if ( listCmd.beanClass == null ) { return null; } List beanList = Collections.EMPTY_LIST; if ( Base.class.isAssignableFrom(listCmd.beanClass) ) { beanList = baseDao.getBases(); } else if ( Course.class.isAssignableFrom(listCmd.beanClass) ) { beanList = courseDao.getCourses(); } else if ( Difficulty.class.isAssignableFrom(listCmd.beanClass) ) { beanList = difficultyDao.getDifficulties(); } else if ( Ethnicity.class.isAssignableFrom(listCmd.beanClass) ) { beanList = ethnicityDao.getEthnicities(); } else if ( Ingredient.class.isAssignableFrom(listCmd.beanClass) ) { beanList = ingredientDao.getIngredients(); } else if ( PrepTime.class.isAssignableFrom(listCmd.beanClass) ) { beanList = prepTimeDao.getPrepTimes(); } listCmd.beans = new ArrayList>(beanList.size()*2); for ( Iterator itr = beanList.iterator(); itr.hasNext(); ) { Object bean = itr.next(); BeanWrapper wrapper = new BeanWrapperImpl(bean); Map beanMap = new LinkedHashMap(); listCmd.beans.add(beanMap); ListMetaData lmd = this.meta.get(listCmd.beanClass.getName()); for ( int j = 0; j < lmd.properties.length; j++ ) { Object value = wrapper.getPropertyValue(lmd.properties[j].name); value = value == null ? "" : value.toString(); String name = lmd.properties[j].name; beanMap.put(name,value); beanMap.put(ORIGINAL_VALUE_PREFIX+name,value); } } Map data = new LinkedHashMap(); data.put("meta",this.meta); return data; } /** * @return the baseDao */ public BaseDao getBaseDao() { return baseDao; } /** * @param baseDao the baseDao to set */ public void setBaseDao(BaseDao baseDao) { this.baseDao = baseDao; } /** * @return the courseDao */ public CourseDao getCourseDao() { return courseDao; } /** * @param courseDao the courseDao to set */ public void setCourseDao(CourseDao courseDao) { this.courseDao = courseDao; } /** * @return the difficultyDao */ public DifficultyDao getDifficultyDao() { return difficultyDao; } /** * @param difficultyDao the difficultyDao to set */ public void setDifficultyDao(DifficultyDao difficultyDao) { this.difficultyDao = difficultyDao; } /** * @return the domainObjectFactory */ public DomainObjectFactory getDomainObjectFactory() { return domainObjectFactory; } /** * @param domainObjectFactory the domainObjectFactory to set */ public void setDomainObjectFactory(DomainObjectFactory domainObjectFactory) { this.domainObjectFactory = domainObjectFactory; } /** * @return the ethnicityDao */ public EthnicityDao getEthnicityDao() { return ethnicityDao; } /** * @param ethnicityDao the ethnicityDao to set */ public void setEthnicityDao(EthnicityDao ethnicityDao) { this.ethnicityDao = ethnicityDao; } /** * @return the ingredientDao */ public IngredientDao getIngredientDao() { return ingredientDao; } /** * @param ingredientDao the ingredientDao to set */ public void setIngredientDao(IngredientDao ingredientDao) { this.ingredientDao = ingredientDao; } /** * @return the meta */ public Map getMeta() { return meta; } /** * @param meta the meta to set */ public void setMeta(Map meta) { this.meta = meta; } /** * @return the prepTimeDao */ public PrepTimeDao getPrepTimeDao() { return prepTimeDao; } /** * @param prepTimeDao the prepTimeDao to set */ public void setPrepTimeDao(PrepTimeDao prepTimeDao) { this.prepTimeDao = prepTimeDao; } /** * @return the recipeDao */ public RecipeDao getRecipeDao() { return recipeDao; } /** * @param recipeDao the recipeDao to set */ public void setRecipeDao(RecipeDao recipeDao) { this.recipeDao = recipeDao; } /** * @return the userBiz */ public UserBiz getUserBiz() { return userBiz; } /** * @param userBiz the userBiz to set */ public void setUserBiz(UserBiz userBiz) { this.userBiz = userBiz; } /** * @return the parameterDao */ public XwebParamDao getParameterDao() { return parameterDao; } /** * @param parameterDao the parameterDao to set */ public void setParameterDao(XwebParamDao parameterDao) { this.parameterDao = parameterDao; } }