1
2
3
4
5
6
7 package org.astrogrid.tableserver.metadata;
8
9 import java.io.IOException;
10 import java.net.URL;
11 import org.apache.commons.logging.Log;
12 import org.apache.commons.logging.LogFactory;
13 import org.astrogrid.cfg.ConfigFactory;
14 import org.astrogrid.cfg.ConfigReader;
15 import org.astrogrid.dataservice.metadata.MetadataException;
16 import org.astrogrid.tableserver.metadata.ColumnInfo;
17 import org.astrogrid.tableserver.metadata.TableInfo;
18 import org.astrogrid.xml.DomHelper;
19 import org.astrogrid.xml.XmlTypes;
20 import org.w3c.dom.Element;
21 import org.xml.sax.SAXException;
22
23 /***
24 * Provides a set of convenience routines for accessing the TableMetaDoc that
25 * defines tabular datasets. Generally speaking we want to
26 * return details from the document instead of the database itself for two reaons:
27 * 1) The database may not be locally available (if it's proxied) or it might not
28 * really exist - the service may be simulating one
29 * 2) The sysadmin will have decided which tables/columns are to be published
30 * and whihc are not, and the ones to be publihsed are in the resource document
31 * 4) There are many extra bits of information that we may need to know (eg units)
32 * that are not available from the db's natural metadata
33
34 */
35
36 public class TableMetaDocInterpreter
37 {
38 Log log = LogFactory.getLog(TableMetaDocInterpreter.class);
39
40 Element metadoc;
41
42 URL docUrl = null;
43
44 public final static String TABLE_METADOC_URL_KEY = "datacenter.metadoc.url";
45 public final static String TABLE_METADOC_FILE_KEY = "datacenter.metadoc.file";
46
47 /*** Construct to interpret the table metadoc given in the config file */
48 public TableMetaDocInterpreter() throws IOException {
49 docUrl = ConfigFactory.getCommonConfig().getUrl(TABLE_METADOC_URL_KEY, null);
50 if (docUrl != null) {
51 loadUrl(docUrl);
52 }
53 else {
54 docUrl = ConfigReader.resolveFilename(ConfigFactory.getCommonConfig().getString(TABLE_METADOC_FILE_KEY));
55 loadUrl(docUrl);
56 }
57 }
58
59
60 /*** Loads metadoc from given URL */
61 private void loadUrl(URL url) throws IOException {
62 try {
63 log.debug("Loading metadoc from "+url);
64 metadoc = DomHelper.newDocument(url).getDocumentElement();
65 }
66 catch (SAXException e) {
67 throw new MetadataException("Server's TableMetaDoc at "+url+" is invalid XML: "+e);
68 }
69 }
70
71
72
73 /*** Returns the element describing the catalog (group of tables, not necessarily
74 * a sky catalog) with the given ID/name.
75 * Looks through all the elements so that it can check case insensitively.
76 */
77 public Element getCatalogElement(String catalogName) {
78
79 Element[] cats = DomHelper.getChildrenByTagName(metadoc, "Catalog");
80
81
82 if (catalogName == null) {
83
84 log.warn("Catalog name is null, assuming first one");
85 return cats[0];
86 }
87
88 for (int i = 0; i < cats.length; i++) {
89 String catId = cats[i].getAttribute("ID");
90 if ((catId == null) || (catId.length()==0)) {
91 catId = DomHelper.getValueOf(cats[i], "Name");
92 }
93 if (catId.trim().toLowerCase().equals(catalogName.toLowerCase())) {
94 return cats[i];
95 }
96 }
97 return null;
98 }
99
100
101 /*** Returns the element describing the table with the given ID/name.
102 * Looks through all the elements so that it can check case insensitively.
103 */
104 public Element getTableElement(Element catalog, String table) {
105
106
107 if (table == null) { throw new IllegalArgumentException("No table specified"); }
108
109 Element[] tables = DomHelper.getChildrenByTagName(catalog, "Table");
110 for (int i = 0; i < tables.length; i++) {
111 String tableId = tables[i].getAttribute("ID");
112 if ((tableId == null) || (tableId.length()==0)) {
113 tableId = DomHelper.getValueOf(tables[i], "Name");
114 }
115 if (tableId.trim().toLowerCase().equals(table.toLowerCase())) {
116 return tables[i];
117 }
118 }
119 return null;
120 }
121
122 /*** Return table info on the column with the given ID/name */
123 public TableInfo getTable(String catalog, String table) {
124 return makeTableInfo(getTableElement(getCatalogElement(catalog), table));
125 }
126
127 /*** Return column info with the given name/id in the given table name/id */
128 public Element getColumnElement(String catalog, String table, String column) {
129
130
131 if (column == null) { throw new IllegalArgumentException("No column specified"); }
132 if (table == null) { throw new IllegalArgumentException("No table specified"); }
133
134 Element tableRes = getTableElement(getCatalogElement(catalog), table);
135
136 if (tableRes == null) {
137
138 throw new IllegalArgumentException("Table '"+table+"' not found in Table MetaDoc at "+docUrl);
139 }
140
141 Element[] cols = DomHelper.getChildrenByTagName(tableRes, "Column");
142
143 if (cols == null) {
144
145 throw new IllegalArgumentException("No Columns found in table "+table);
146 }
147
148 for (int i = 0; i < cols.length; i++) {
149 String colName = DomHelper.getValueOf(DomHelper.getSingleChildByTagName(cols[i], "Name"));
150 if (colName.trim().toLowerCase().equals(column.toLowerCase())) {
151 return cols[i];
152 }
153 }
154 return null;
155 }
156
157 /*** Return column info with the given name/id in the given table name/id */
158 public ColumnInfo getColumn(String datasetId, String table, String column) throws MetadataException {
159 return makeColumnInfo(table, getColumnElement(datasetId, table, column));
160 }
161
162 /*** Used by SQL results where only the column name is known. Returns the element
163 * corresponding to the only matching column, if there are several or none
164 * throws an exception*/
165 public ColumnInfo guessColumn(String[] scope, String column) throws IOException {
166
167 Element foundCol = null;
168 String foundTable = null;
169
170
171 String[] cats = getCatalogs();
172 for (int d = 0; d < cats.length; d++) {
173
174 Element catNode = getCatalogElement(cats[d]);
175
176
177 if (scope==null) {
178 Element[] tables = DomHelper.getChildrenByTagName(catNode, "Table");
179 scope = new String[tables.length];
180 for (int i = 0; i < scope.length; i++) {
181 scope[i] = DomHelper.getValueOf(DomHelper.getSingleChildByTagName(tables[i], "Name"));
182 }
183 }
184
185 for (int t = 0; t < scope.length; t++) {
186 String tableName = scope[t];
187
188 Element[] cols = DomHelper.getChildrenByTagName(getTableElement(catNode, tableName), "Column");
189 for (int c = 0; c < cols.length; c++) {
190 String colName = DomHelper.getValueOf(DomHelper.getSingleChildByTagName(cols[c], "Name"));
191 if (colName.trim().toLowerCase().equals(column.toLowerCase())) {
192 if (foundCol == null) {
193 foundCol = cols[c];
194 foundTable = tableName;
195 }
196 else {
197 throw new MetadataException("Column "+column+" found more than once");
198 }
199 }
200 }
201 }
202 }
203 if (foundCol == null) {
204 throw new MetadataException("Column "+column+" not found in any table");
205 }
206 else {
207 return makeColumnInfo(foundTable, foundCol);
208 }
209 }
210
211 /*** Returns the list of names of all the catalogs in the metadoc */
212 public String[] getCatalogs() {
213 Element[] elements = DomHelper.getChildrenByTagName(metadoc, "Catalog");
214 String[] catNames = new String[elements.length];
215 for (int i = 0; i < elements.length; i++)
216 {
217 catNames[i] = DomHelper.getValueOf(elements[i], "Name");
218 }
219 return catNames;
220 }
221
222 public TableInfo[] getTables(String catalog) {
223 Element[] elements = DomHelper.getChildrenByTagName(getCatalogElement(catalog), "Table");
224 TableInfo[] infos = new TableInfo[elements.length];
225 for (int i = 0; i < elements.length; i++)
226 {
227 infos[i] = makeTableInfo(elements[i]);
228 infos[i].setDataset(catalog);
229 }
230 return infos;
231 }
232
233 public ColumnInfo[] getColumns(String catalog, String table) throws MetadataException {
234 Element tableElement = getTableElement(getCatalogElement(catalog), table);
235 Element[] elements = DomHelper.getChildrenByTagName(tableElement, "Column");
236 ColumnInfo[] infos = new ColumnInfo[elements.length];
237 for (int i = 0; i < elements.length; i++)
238 {
239 infos[i] = makeColumnInfo(table, elements[i]);
240 }
241 return infos;
242 }
243
244
245 /*** Creates a TableInfo instance from the given RDBMS Resource table element */
246 public TableInfo makeTableInfo(Element element) {
247
248 if (element == null) { return null; }
249
250 TableInfo info = new TableInfo();
251 info.setName(DomHelper.getValueOf(element, "Name"));
252 String id = element.getAttribute("ID");
253 if (id == null) id = info.getName();
254 info.setId(id);
255 info.setDescription(nullIfEmpty(DomHelper.getValueOf(element, "Description")));
256 return info;
257 }
258
259 /*** Creates a ColumnInfo instance from the given RDBMS Resource column element */
260 public ColumnInfo makeColumnInfo(String table, Element element) throws MetadataException {
261
262 if (element == null) { return null; }
263
264 ColumnInfo info = new ColumnInfo();
265 info.setName(DomHelper.getValueOf(element, "Name"));
266 info.setGroup(table);
267
268 String id = element.getAttribute("ID");
269 if ((id == null) || (id.length()==0)) {
270 id = info.getGroup()+"."+info.getName();
271 }
272 info.setId(id);
273 info.setDescription(nullIfEmpty(DomHelper.getValueOf(element, "Description")));
274 info.setUnits(nullIfEmpty(DomHelper.getValueOf(element, "Units")));
275 info.setErrorField(nullIfEmpty(DomHelper.getValueOf(element, "ErrorColumn")));
276
277 Element[] ucdNodes = DomHelper.getChildrenByTagName(element, "UCD");
278 for (int u = 0; u < ucdNodes.length; u++) {
279 info.setUcd(DomHelper.getValueOf(ucdNodes[u]), ucdNodes[u].getAttribute("version"));
280 }
281
282 String datatype = nullIfEmpty(DomHelper.getValueOf(element, "Datatype"));
283 if (datatype == null) {
284 throw new MetadataException("Column "+info.getName()+" has no Datatype element in metadoc");
285 }
286
287 info.setPublicType(datatype);
288 if (info.getPublicType() != null) {
289 info.setJavaType(XmlTypes.getJavaType(info.getPublicType()));
290 }
291 return info;
292 }
293
294 /*** Returns null if given string is empty/whitespace, or the string otherwise.
295 * This makes it easier to check for unknown properties, such as UCDs, where you can
296 * just check for nulls */
297 public String nullIfEmpty(String s) {
298 if (s.trim().length()==0) {
299 return null;
300 }
301 return s;
302 }
303
304 /*** Returns a mini 'catalog' that lists the tables and fields that define
305 * sky coordinates, for things like cone searches */
306
307
308 }
309
310