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