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    }