1 /** |
|
2 * SQL-DK |
|
3 * Copyright © 2013 František Kučera (frantovo.cz) |
|
4 * |
|
5 * This program is free software: you can redistribute it and/or modify |
|
6 * it under the terms of the GNU General Public License as published by |
|
7 * the Free Software Foundation, either version 3 of the License, or |
|
8 * (at your option) any later version. |
|
9 * |
|
10 * This program is distributed in the hope that it will be useful, |
|
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13 * GNU General Public License for more details. |
|
14 * |
|
15 * You should have received a copy of the GNU General Public License |
|
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
17 */ |
|
18 package info.globalcode.sql.dk.formatting; |
|
19 |
|
20 import info.globalcode.sql.dk.Parameter; |
|
21 import info.globalcode.sql.dk.Xmlns; |
|
22 import info.globalcode.sql.dk.configuration.DatabaseDefinition; |
|
23 import static info.globalcode.sql.dk.Functions.notNull; |
|
24 import info.globalcode.sql.dk.NamedParameter; |
|
25 import info.globalcode.sql.dk.configuration.PropertyDeclaration; |
|
26 import static info.globalcode.sql.dk.formatting.AbstractXmlFormatter.qname; |
|
27 import java.sql.Array; |
|
28 import java.sql.ResultSet; |
|
29 import java.sql.SQLException; |
|
30 import java.sql.SQLXML; |
|
31 import java.util.ArrayList; |
|
32 import java.util.LinkedHashMap; |
|
33 import java.util.List; |
|
34 import java.util.Map; |
|
35 import java.util.logging.Level; |
|
36 import java.util.logging.Logger; |
|
37 import javax.xml.namespace.QName; |
|
38 |
|
39 /** |
|
40 * <p> |
|
41 * Prints machine-readable output – XML document containing resultsets and updates count. Good |
|
42 * choice for further processing – e.g. XSL transformation.</p> |
|
43 * |
|
44 * <p> |
|
45 * TODO: XSD</p> |
|
46 * |
|
47 * @author Ing. František Kučera (frantovo.cz) |
|
48 */ |
|
49 @PropertyDeclaration(name = XmlFormatter.PROPERTY_LABELED_COLUMNS, defaultValue = "false", type = Boolean.class, description = "whether to add 'label' attribute to each 'column' element") |
|
50 public class XmlFormatter extends AbstractXmlFormatter { |
|
51 |
|
52 public static final String NAME = "xml"; // bash-completion:formatter |
|
53 public static final String PROPERTY_LABELED_COLUMNS = "labeledColumns"; |
|
54 private static final Logger log = Logger.getLogger(XmlFormatter.class.getName()); |
|
55 private final boolean labeledColumns; |
|
56 |
|
57 public XmlFormatter(FormatterContext formatterContext) { |
|
58 super(formatterContext); |
|
59 labeledColumns = formatterContext.getProperties().getBoolean(PROPERTY_LABELED_COLUMNS, false); |
|
60 } |
|
61 |
|
62 @Override |
|
63 public void writeStartBatch() { |
|
64 super.writeStartBatch(); |
|
65 printStartDocument(); |
|
66 printStartElement(qname("batchResult"), singleAttribute(qname("xmlns"), Xmlns.BATCH_RESULT)); |
|
67 } |
|
68 |
|
69 @Override |
|
70 public void writeEndBatch() { |
|
71 super.writeEndBatch(); |
|
72 printEndElement(); |
|
73 printEndDocument(); |
|
74 } |
|
75 |
|
76 @Override |
|
77 public void writeStartDatabase(DatabaseDefinition databaseDefinition) { |
|
78 super.writeStartDatabase(databaseDefinition); |
|
79 Map<QName, String> attributes = databaseDefinition.getName() == null ? null : singleAttribute(qname("name"), databaseDefinition.getName()); |
|
80 printStartElement(qname("database"), attributes); |
|
81 } |
|
82 |
|
83 @Override |
|
84 public void writeEndDatabase() { |
|
85 super.writeEndDatabase(); |
|
86 printEndElement(); |
|
87 } |
|
88 |
|
89 @Override |
|
90 public void writeStartStatement() { |
|
91 super.writeStartStatement(); |
|
92 printStartElement(qname("statement")); |
|
93 } |
|
94 |
|
95 @Override |
|
96 public void writeEndStatement() { |
|
97 super.writeEndStatement(); |
|
98 printEndElement(); |
|
99 } |
|
100 |
|
101 @Override |
|
102 public void writeQuery(String sql) { |
|
103 super.writeQuery(sql); |
|
104 printTextElement(qname("sql"), null, sql); |
|
105 } |
|
106 |
|
107 @Override |
|
108 public void writeParameters(List<? extends Parameter> parameters) { |
|
109 super.writeParameters(parameters); |
|
110 |
|
111 for (Parameter p : notNull(parameters)) { |
|
112 |
|
113 Map<QName, String> attributes = new LinkedHashMap<>(2); |
|
114 if (p instanceof NamedParameter) { |
|
115 attributes.put(qname("name"), ((NamedParameter) p).getName()); |
|
116 } |
|
117 attributes.put(qname("type"), p.getType().name()); |
|
118 |
|
119 printTextElement(qname("parameter"), attributes, String.valueOf(p.getValue())); |
|
120 } |
|
121 |
|
122 } |
|
123 |
|
124 @Override |
|
125 public void writeStartResultSet(ColumnsHeader header) { |
|
126 super.writeStartResultSet(header); |
|
127 printStartElement(qname("resultSet")); |
|
128 |
|
129 for (ColumnDescriptor cd : header.getColumnDescriptors()) { |
|
130 Map<QName, String> attributes = new LinkedHashMap<>(4); |
|
131 attributes.put(qname("label"), cd.getLabel()); |
|
132 attributes.put(qname("name"), cd.getName()); |
|
133 attributes.put(qname("typeName"), cd.getTypeName()); |
|
134 attributes.put(qname("type"), String.valueOf(cd.getType())); |
|
135 printEmptyElement(qname("columnHeader"), attributes); |
|
136 } |
|
137 } |
|
138 |
|
139 @Override |
|
140 public void writeEndResultSet() { |
|
141 super.writeEndResultSet(); |
|
142 printEndElement(); |
|
143 } |
|
144 |
|
145 @Override |
|
146 public void writeStartRow() { |
|
147 super.writeStartRow(); |
|
148 printStartElement(qname("row")); |
|
149 } |
|
150 |
|
151 @Override |
|
152 public void writeColumnValue(Object value) { |
|
153 super.writeColumnValue(value); |
|
154 |
|
155 Map<QName, String> attributes = null; |
|
156 if (labeledColumns) { |
|
157 attributes = new LinkedHashMap<>(2); |
|
158 attributes.put(qname("label"), getCurrentColumnsHeader().getColumnDescriptors().get(getCurrentColumnsCount() - 1).getLabel()); |
|
159 } |
|
160 |
|
161 if (value == null) { |
|
162 if (attributes == null) { |
|
163 attributes = new LinkedHashMap<>(2); |
|
164 } |
|
165 attributes.put(qname("null"), "true"); |
|
166 printEmptyElement(qname("column"), attributes); |
|
167 } else if (value instanceof Array) { |
|
168 |
|
169 Array sqlArray = (Array) value; |
|
170 try { |
|
171 Object[] array = (Object[]) sqlArray.getArray(); |
|
172 printStartElement(qname("column"), attributes); |
|
173 printArray(array); |
|
174 printEndElement(); |
|
175 } catch (SQLException e) { |
|
176 // FIXME: rewrite array formatting, remember array mode, don't try sqlArray.getArray() again and again if it has failed |
|
177 log.log(Level.SEVERE, "Unable to format array", e); |
|
178 try { |
|
179 ResultSet arrayResultSet = sqlArray.getResultSet(); |
|
180 //int columnCount = arrayResultSet.getMetaData().getColumnCount(); |
|
181 ArrayList<Object> arrayList = new ArrayList<>(); |
|
182 while (arrayResultSet.next()) { |
|
183 arrayList.add(arrayResultSet.getObject(2)); |
|
184 // for (int i = 1; i <= columnCount; i++) { |
|
185 // log.log(Level.INFO, "Array column {0} = {1}", new Object[]{i, arrayResultSet.getObject(i)}); |
|
186 // } |
|
187 } |
|
188 |
|
189 printStartElement(qname("column"), attributes); |
|
190 // FIXME: instanceof SQLXML, see below |
|
191 printArray(arrayList.toArray()); |
|
192 printEndElement(); |
|
193 |
|
194 } catch (SQLException e2) { |
|
195 // FIXME: fix logging, error recovery |
|
196 log.log(Level.SEVERE, "Second level fuck up !!!", e2); |
|
197 } |
|
198 |
|
199 writeColumnValue(String.valueOf(value)); |
|
200 } |
|
201 |
|
202 } else if (value instanceof SQLXML) { // FIXME: move to separate method, to AbstractFormatter? |
|
203 SQLXML xml = (SQLXML) value; |
|
204 // TODO: parse DOM/SAX and transplant XML, don't escape (optional) |
|
205 try { |
|
206 printTextElement(qname("column"), attributes, xml.getString()); |
|
207 } catch (SQLException e) { |
|
208 log.log(Level.SEVERE, "Unable to format XML", e); |
|
209 writeColumnValue(String.valueOf(value)); |
|
210 } |
|
211 } else { |
|
212 printTextElement(qname("column"), attributes, toString(value)); |
|
213 } |
|
214 } |
|
215 |
|
216 private void printArray(Object[] array) { |
|
217 printStartElement(qname("array")); |
|
218 for (Object o : array) { |
|
219 if (o instanceof Object[]) { |
|
220 printStartElement(qname("item")); |
|
221 printArray((Object[]) o); |
|
222 printEndElement(); |
|
223 } else { |
|
224 printTextElement(qname("item"), null, String.valueOf(o)); |
|
225 } |
|
226 } |
|
227 printEndElement(); |
|
228 } |
|
229 |
|
230 @Override |
|
231 public void writeEndRow() { |
|
232 super.writeEndRow(); |
|
233 printEndElement(); |
|
234 } |
|
235 |
|
236 @Override |
|
237 public void writeUpdatesResult(int updatedRowsCount) { |
|
238 super.writeUpdatesResult(updatedRowsCount); |
|
239 printTextElement(qname("updatedRows"), null, String.valueOf(updatedRowsCount)); |
|
240 } |
|
241 |
|
242 protected String toString(Object value) { |
|
243 return String.valueOf(value); |
|
244 } |
|
245 } |
|