package com.robonobo.common.serialization; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.robonobo.common.exceptions.SeekInnerCalmException; /** * [De]Serializes a config bean to/from an easy-to-read text file. Note, config * beans can only have string, int, long, double or boolean properties */ public class ConfigBeanSerializer { Log log; public ConfigBeanSerializer() { this(true); } public ConfigBeanSerializer(boolean useLogging) { if(useLogging) log = LogFactory.getLog(getClass()); } public void serializeConfig(Object bean, File outFile) throws IOException { try { BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass()); StringBuffer sb = new StringBuffer(); for (PropertyDescriptor propDesc : beanInfo.getPropertyDescriptors()) { if(propDesc.getName().equals("class")) continue; if(!propTypeOk(propDesc.getPropertyType())) throw new IOException("Invalid property type for property '"+propDesc.getName()+"' in bean class "+bean.getClass()); Object propVal = propDesc.getReadMethod().invoke(bean); if(propVal == null) continue; sb.append(propDesc.getName()); sb.append("="); sb.append(propVal); sb.append("\n"); } PrintWriter out = new PrintWriter(outFile); out.print(sb.toString()); out.close(); } catch (IntrospectionException e) { throw new IOException("Caught "+e.getClass().getName()+" getting beanInfo: "+e.getMessage()); } catch (IllegalArgumentException e) { throw new IOException("Caught "+e.getClass().getName()+" getting beanInfo: "+e.getMessage()); } catch (IllegalAccessException e) { throw new IOException("Caught "+e.getClass().getName()+" getting beanInfo: "+e.getMessage()); } catch (InvocationTargetException e) { throw new IOException("Caught "+e.getClass().getName()+" getting beanInfo: "+e.getMessage()); } } public <T> T deserializeConfig(Class<T> beanClass, File inFile) throws IOException { BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(inFile))); T bean; Pattern cfgPat = Pattern.compile("^(.+?)=(.*)$"); Pattern commentPat = Pattern.compile("^\\s*#.*$"); try { bean = beanClass.newInstance(); Map<String, PropertyDescriptor> propsByName = mapPropsByName(beanClass); String line; while((line = in.readLine()) != null) { if(line.trim().length() == 0) continue; if(commentPat.matcher(line).matches()) continue; Matcher m = cfgPat.matcher(line); if(!m.matches()) throw new IOException("File "+inFile.getAbsolutePath()+" is in unexpected format, line: "+line); String propName = m.group(1); String propVal = m.group(2); if(propVal.length() == 0) continue; PropertyDescriptor prop = propsByName.get(propName); if(prop == null) { if(log != null) log.error("Error deserializing config "+inFile.getAbsolutePath()+": unknown property name "+propName); continue; } if(!propTypeOk(prop.getPropertyType())) throw new IOException("Invalid property type for property '"+prop.getName()+"' in bean class "+bean.getClass()); prop.getWriteMethod().invoke(bean, getPropValue(prop.getPropertyType(), propVal)); } } catch (InstantiationException e) { throw new IOException("Caught "+e.getClass().getName()+" getting beanInfo: "+e.getMessage()); } catch (IllegalAccessException e) { throw new IOException("Caught "+e.getClass().getName()+" getting beanInfo: "+e.getMessage()); } catch (IntrospectionException e) { throw new IOException("Caught "+e.getClass().getName()+" getting beanInfo: "+e.getMessage()); } catch (IllegalArgumentException e) { throw new IOException("Caught "+e.getClass().getName()+" getting beanInfo: "+e.getMessage()); } catch (InvocationTargetException e) { throw new IOException("Caught "+e.getClass().getName()+" getting beanInfo: "+e.getMessage()); } return bean; } /** * Overrides this config bean with environment variables of the form 'cfg_<beanName>_<propName>' */ public void overrideCfgFromEnv(Object bean, String beanName) throws IOException { Map<String, String> env = System.getenv(); try { BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass()); Map<String, PropertyDescriptor> propsByName = mapPropsByName(bean.getClass()); Pattern cfgNamePat = Pattern.compile("^cfg_(.+)_(.+)$"); for (String envName : env.keySet()) { Matcher m = cfgNamePat.matcher(envName); if(!m.matches()) continue; String thisBeanName = m.group(1); if(!thisBeanName.equalsIgnoreCase(beanName)) continue; String propName = m.group(2); String propVal = env.get(envName); PropertyDescriptor prop = propsByName.get(propName); if(prop == null) throw new SeekInnerCalmException("Failed to overwrite config: unknown property "+propName+" in bean "+beanName); if(!propTypeOk(prop.getPropertyType())) throw new IOException("Invalid property type for property '"+prop.getName()+"' in bean class "+bean.getClass()); prop.getWriteMethod().invoke(bean, getPropValue(prop.getPropertyType(), propVal)); if(log != null) log.info("Overriding '"+thisBeanName+"' cfg prop '"+propName+"' with value '"+propVal+"'"); } } catch (IllegalAccessException e) { throw new IOException("Caught "+e.getClass().getName()+" getting beanInfo: "+e.getMessage()); } catch (IntrospectionException e) { throw new IOException("Caught "+e.getClass().getName()+" getting beanInfo: "+e.getMessage()); } catch (IllegalArgumentException e) { throw new IOException("Caught "+e.getClass().getName()+" getting beanInfo: "+e.getMessage()); } catch (InvocationTargetException e) { throw new IOException("Caught "+e.getClass().getName()+" getting beanInfo: "+e.getMessage()); } } private Object getPropValue(Class<?> propType, String propValStr) throws IOException { Object propVal; if (propType.equals(Integer.TYPE) || Integer.class.isAssignableFrom(propType)) propVal = Integer.valueOf(propValStr); else if (propType.equals(Long.TYPE) || Long.class.isAssignableFrom(propType)) propVal = Long.valueOf(propValStr); else if(propType.equals(Double.TYPE) || Double.class.isAssignableFrom(propType)) propVal = Double.valueOf(propValStr); else if (propType.equals(Boolean.TYPE) || Boolean.class.isAssignableFrom(propType)) propVal = Boolean.valueOf(propValStr); else if (String.class.isAssignableFrom(propType)) propVal = propValStr; else throw new SeekInnerCalmException(); return propVal; } private Map<String, PropertyDescriptor> mapPropsByName(Class<?> clazz) throws IntrospectionException { Map<String, PropertyDescriptor> result = new HashMap<String, PropertyDescriptor>(); BeanInfo bi = Introspector.getBeanInfo(clazz); for (PropertyDescriptor pd : bi.getPropertyDescriptors()) { result.put(pd.getName(), pd); } return result; } private boolean propTypeOk(Class<?> propType) { if (propType.equals(Integer.TYPE) || Integer.class.isAssignableFrom(propType)) return true; if (propType.equals(Long.TYPE) || Long.class.isAssignableFrom(propType)) return true; if (propType.equals(Boolean.TYPE) || Boolean.class.isAssignableFrom(propType)) return true; if(propType.equals(Double.TYPE) || Double.class.isAssignableFrom(propType)) return true; if (String.class.isAssignableFrom(propType)) return true; return false; } }