001 /* 002 @license.text@ 003 */ 004 package biz.hammurapi.sql.columns; 005 006 import java.io.Serializable; 007 import java.sql.PreparedStatement; 008 import java.sql.ResultSet; 009 import java.sql.ResultSetMetaData; 010 import java.sql.SQLException; 011 import java.util.HashMap; 012 import java.util.Map; 013 014 import org.w3c.dom.Element; 015 import org.w3c.dom.Node; 016 import org.xml.sax.ContentHandler; 017 import org.xml.sax.SAXException; 018 019 import biz.hammurapi.config.ConfigurationException; 020 import biz.hammurapi.config.Context; 021 import biz.hammurapi.config.ContextConfigurable; 022 import biz.hammurapi.config.DomConfigFactory; 023 import biz.hammurapi.config.DomConfigurable; 024 import biz.hammurapi.convert.ConvertingService; 025 import biz.hammurapi.sql.SQLExceptionEx; 026 import biz.hammurapi.util.Attributable; 027 import biz.hammurapi.xml.dom.AbstractDomObject; 028 import biz.hammurapi.xml.dom.DOMUtils; 029 import biz.hammurapi.xml.dom.DomSerializable; 030 031 032 /** 033 * Base class for different column types 034 * @author Pavel Vlasov 035 * @version $Revision: 1.14 $ 036 */ 037 public abstract class Column 038 implements 039 Cloneable, 040 ContextConfigurable, 041 DomConfigurable, 042 Attributable, 043 Serializable { 044 045 private ColumnChangeListener listener; 046 private String name; 047 private boolean isModified; 048 private boolean isPrimaryKey; 049 private String label; 050 private String xmlName; 051 052 public abstract void setOriginal(); 053 054 /** 055 * Clears modified flag. 056 */ 057 public void clearModified() { 058 isModified=false; 059 } 060 061 /** 062 * Sets field value to default and clears modified flag. 063 * 064 */ 065 public abstract void clear(); 066 067 /** 068 * @param listener The listener to set. 069 */ 070 public void setListener(ColumnChangeListener listener) { 071 this.listener = listener; 072 } 073 074 /** 075 * @return Returns the listener. 076 */ 077 public ColumnChangeListener getListener() { 078 return listener; 079 } 080 081 protected void onChange() { 082 isModified=true; 083 if (listener!=null) { 084 listener.onChange(this); 085 } 086 } 087 088 /** 089 * @param name Column name 090 * @param isPrimaryKey 'true' if column is part of primary key 091 */ 092 public Column(String name, boolean isPrimaryKey) { 093 super(); 094 this.name = name; 095 this.xmlName = name==null ? null : name.toLowerCase().replace('_', '-'); 096 this.isPrimaryKey = isPrimaryKey; 097 } 098 099 /** 100 * @return Column name if value was modified, null otherwise. 101 */ 102 public String listName() { 103 return isModified ? name : null; 104 } 105 106 /** 107 * Parameterizes prepared statement. 108 * @param ps 109 * @param idx Parameter index 110 * @param force If true column is treated a modified 111 * @return idx+1 if column was modified and prepared statement was parameterized, idx otherwise. 112 */ 113 public int parameterize(PreparedStatement ps, int idx, boolean force) throws SQLException { 114 if (isModified || force) { 115 try { 116 parameterizeInternal(ps, idx); 117 } catch (SQLException e) { 118 throw new SQLExceptionEx("Could not parameterize "+getName()+": "+e, e); 119 } 120 return idx+1; 121 } 122 123 return idx; 124 } 125 126 /** 127 * Implement this method in subclasses. 128 */ 129 protected abstract void parameterizeInternal(PreparedStatement ps, int idx) throws SQLException; 130 131 /** 132 * Implement this method in subclasses. 133 */ 134 public abstract void parameterizeOriginal(PreparedStatement ps, int idx) throws SQLException; 135 136 /** 137 * @return Returns the isPrimaryKey. 138 */ 139 public boolean isPrimaryKey() { 140 return isPrimaryKey; 141 } 142 143 /** 144 * @return name 145 */ 146 public String getName() { 147 return name; 148 } 149 150 public String getXmlName() { 151 return xmlName; 152 } 153 154 /** 155 * Returns column value as object 156 * @param originalValue if true this method returns original value instead of current value. 157 * @return 158 */ 159 public abstract Object getObjectValue(boolean originalValue); 160 161 /** 162 * @param owner 163 */ 164 public void toDom(Element owner, String nodeName, boolean originalValue) { 165 // String nodeName = domName==null ? name : domName; 166 if (nodeName==null) { 167 nodeName=getName(); 168 } 169 170 Object objectValue=getObjectValue(false); 171 172 if (nodeName.length()==0) { 173 // Zero mapping 174 return; 175 } 176 177 if (nodeName.startsWith("@")) { 178 // Attribute mapping 179 if (objectValue!=null) { 180 owner.setAttribute(nodeName.substring(1), objectValue.toString()); 181 } 182 } else if (nodeName.equals(".")) { 183 // Text mapping 184 if (objectValue!=null) { 185 owner.appendChild(owner.getOwnerDocument().createTextNode(objectValue.toString())); 186 } 187 } else if (nodeName.startsWith("!")) { 188 // Simple mapping 189 if (objectValue!=null) { 190 Element el=owner.getOwnerDocument().createElement(nodeName.substring(1)); 191 owner.appendChild(el); 192 el.appendChild(owner.getOwnerDocument().createTextNode(objectValue.toString())); 193 } 194 } else { 195 // full mapping 196 Element el=owner.getOwnerDocument().createElement(nodeName); 197 owner.appendChild(el); 198 199 if (objectValue==null) { 200 el.setAttribute("is-null", "yes"); 201 } else { 202 DomSerializable cds = (DomSerializable) ConvertingService.convert(objectValue, DomSerializable.class); 203 cds.toDom(el); 204 } 205 206 if (isPrimaryKey) { 207 el.setAttribute("primary-key", "yes"); 208 } 209 if (isModified) { 210 el.setAttribute("modified", "yes"); 211 } 212 213 if (label!=null) { 214 el.setAttribute("label", label); 215 } 216 217 el.setAttribute("column-type", getType()); 218 219 el.setAttribute("align", getAlignment()); 220 221 if (!attributes.isEmpty()) { 222 DomSerializable ads = (DomSerializable) ConvertingService.convert(attributes, DomSerializable.class); 223 ads.toDom(AbstractDomObject.addElement(el, "column-attributes")); 224 } 225 } 226 } 227 228 protected abstract String getType(); 229 230 /** 231 * @return 232 */ 233 protected String getAlignment() { 234 return "left"; 235 } 236 237 /** 238 * @param typeName 239 * @return Column type for Java type 240 */ 241 public static Class columnType(String typeName) { 242 if ("boolean".equals(typeName)) { 243 return BooleanColumn.class; 244 } else if ("byte".equals(typeName)) { 245 return ByteColumn.class; 246 } else if ("char".equals(typeName)) { 247 return CharColumn.class; 248 } else if ("double".equals(typeName)) { 249 return DoubleColumn.class; 250 } else if ("float".equals(typeName)) { 251 return FloatColumn.class; 252 } else if ("int".equals(typeName)) { 253 return IntColumn.class; 254 } else if ("long".equals(typeName)) { 255 return LongColumn.class; 256 } else if ("short".equals(typeName)) { 257 return ShortColumn.class; 258 } else { 259 return ObjectColumn.class; 260 } 261 } 262 263 /** 264 * @return Returns the isModified. 265 */ 266 public boolean isModified() { 267 return isModified; 268 } 269 270 protected boolean force; 271 // private String domName; 272 273 /** 274 * @param force 275 */ 276 public void setForce(boolean force) { 277 this.force=force; 278 } 279 280 /** 281 * @param rs 282 * @param name 283 * @return True if result set has a column with specified name. 284 * @throws SQLException 285 */ 286 public static boolean hasColumn(ResultSet rs, String name) throws SQLException { 287 ResultSetMetaData metaData = rs.getMetaData(); 288 for (int i=1, c=metaData.getColumnCount(); i<=c; i++) { 289 if (name.equals(metaData.getColumnName(i))) { 290 return true; 291 } 292 } 293 return false; 294 } 295 296 /** 297 * Loads value from XML Element 298 * @param textValue Text value 299 */ 300 public abstract void load(String textValue); 301 302 public Object clone() throws CloneNotSupportedException { 303 return super.clone(); 304 } 305 306 /** 307 * @return display label for column 308 */ 309 public String getLabel() { 310 return label; 311 } 312 313 /** 314 * Sets display label for column 315 * @param label 316 */ 317 public void setLabel(String label) { 318 this.label = label; 319 } 320 321 // /** 322 // * @param domName 323 // */ 324 // public void setDomName(String domName) { 325 // this.domName=domName; 326 // } 327 // 328 // public String getDomName() { 329 // return domName; 330 // } 331 332 /** 333 * Copies values from source column 334 * @param source 335 */ 336 public abstract void set(Column source); 337 338 private Map attributes=new HashMap(); 339 340 public void setAttribute(Object key, Object value) { 341 attributes.put(key, value); 342 } 343 344 public Object getAttribute(Object key) { 345 return attributes.get(key); 346 } 347 348 public Object removeAttribute(Object key) { 349 return attributes.remove(key); 350 } 351 352 public void configure(Node configNode, Context context, ClassLoader classLoader) throws ConfigurationException { 353 if (!((Element) configNode).getAttribute("is-null").equals("yes")) { 354 load(AbstractDomObject.getElementText(configNode)); 355 } 356 357 DomConfigFactory dcf=new DomConfigFactory(); 358 attributes.clear(); 359 try { 360 Node attributesNode = DOMUtils.selectSingleNode(configNode, "column-attributes"); 361 if (attributesNode!=null) { 362 attributes.putAll((Map) dcf.create(attributesNode)); 363 } 364 } catch (Exception e) { 365 throw new ConfigurationException("Cannot load column attributes: "+e, e); 366 } 367 368 } 369 370 public void toSax(String uri, ContentHandler contentHandler) throws SAXException { 371 Object objectValue=getObjectValue(false); 372 if (objectValue!=null && xmlName!=null) { 373 contentHandler.startElement(uri, xmlName, xmlName, null); 374 char[] strValue = String.valueOf(objectValue).toCharArray(); // TODO SAX Serializable, composite serializer like with DOM. 375 contentHandler.characters(strValue, 0, strValue.length); 376 contentHandler.endElement(uri, xmlName, xmlName); 377 } 378 } 379 }