Source code: org/jdaemon/era/xml/AbstractXMLCube.java
1 /*
2 * AbstractXMLCube.java
3 *
4 * Copyright (C) 2002 Jan Stanger
5 * This program is distributed under the terms of the Lesser GNU General Public
6 * License (v2 or later) per the included COPYING.txt file, or see www.fsf.org.
7 *
8 * Created in June 2002
9 */
10
11 package org.jdaemon.era.xml;
12
13 import org.jdaemon.era.*;
14 import org.jdaemon.util.*;
15
16 //Standard Java imports
17 import java.io.*;
18 import java.text.*;
19 import java.util.*;
20
21 //XML related imports
22 import javax.xml.parsers.*;
23 import org.w3c.dom.*;
24
25 //Log4J
26 import org.apache.log4j.Logger;
27
28 import org.apache.xml.serialize.*;
29
30 /**
31 * Abstract implementation of XMLCube. Extends AbstractCube (for completeness --
32 * even though it does not reuse much -- may have to revise this class or AbstractCube).
33 * Note that AbstractCube more closely matches the requirements of the JavaCube than the XMLCube.
34 *
35 * @author Jan Stanger, modified by Nimish Shah
36 */
37 public abstract class AbstractXMLCube extends AbstractCube implements XMLCube {
38
39 /** Logger */
40 private static Logger log = Logger.getLogger("org.jdaemon.era.xml.AbstractXMLCube");
41
42 /** XML DOM tree */
43 protected Document xmlDoc;
44 protected String xmlString;
45
46 /** Descriptor containing meta data about Cube */
47 protected CubeDescriptor descriptor;
48
49 public AbstractXMLCube() {}
50
51 /**
52 * Create new cube containing a subset of the values in this Cube
53 */
54 public Cube constrain(Filter filter) {
55 return new FilteredXMLCube(this, filter);
56 }
57
58 /**
59 * Create new cube containing a subset of the values in this Cube
60 */
61 public Cube constrain(String attribute, int operator, Object operand) {
62 return new FilteredXMLCube(this, getFilter(attribute, operator, operand));
63 }
64
65 /**
66 * Obtain a filter object for use in a constrained cube (e.g. FilteredXMLCube)
67 *
68 * @param attribute Attribute name to filter by
69 * @param contraintType Type of constraint (see XMLFilters)
70 * @param value Value to filter by
71 * @return StandardFilter object representing the filtering criteria
72 */
73 public XMLFilter getFilter(String attribute, int constraintType, Object value) {
74 return new XMLFilters().getFilter(attribute, constraintType, value);
75 }
76
77 /**
78 * Generic method for adding data from an existing Cube (of whatever flavor)
79 * to this XMLCube in the form of an XML DOM tree. It makes heavy use of
80 * the meta data stored in the Descriptor of that Cube.
81 *
82 * @param results Query results (as Cube)
83 */
84 public void addAll(Cube results) {
85 log.debug("addAll: 'results' (input Cube) instance of: " + results.getClass().getName());
86
87 DecimalFormat decFmt = (DecimalFormat)DecimalFormat.getInstance();
88 decFmt.applyPattern("############0.00000"); //Chopping off decimals that are too long.
89
90 //Retrieving Descriptor from Cube
91 descriptor = results.getDescriptor();
92
93 log.debug("addAll: Descriptor: " + descriptor);
94
95 try {
96 //Creating empty in-memory XML document
97 xmlDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
98 }
99 catch (ParserConfigurationException e) {
100 //Decided not to throw this exception because it is VERY unlikely that this error
101 //will ever occur, therefore it shouldn't be required for callers to catch this
102 //exception. Instead, CubeException is only instantiated for error logging purposes.
103 new CubeException(this, "addAll(Cube)", "Exception during instantiation of new Document using the DocumentBuilderFactory", e);
104 //Since we 'swallow' this exception, we at least want to see the complete error stack
105 e.printStackTrace();
106 }
107
108 //Creating root node and adding it to XML document
109 Node root = addXMLNode(xmlDoc, xmlDoc, "DataSet", "Root");
110
111 //Add initial meta data section
112 Node metaData = addXMLNode(xmlDoc, root, "ResultSetMetaData");
113
114 //Iterators & temp variables for looping through all rows of query result
115 Iterator it = results.iterator();
116 Object currRow;
117 String currAttrNm;
118 Object currAttrVal;
119 Iterator attributeIt;
120
121 //Temp variables for meta-data while loop
122 String currDType = "";
123 HashMap currElemAttribs;
124
125 log.debug("addAll: Check if Iterator is null: " + it);
126
127 log.debug("addAll: Check if Descriptor is null: " + descriptor);
128
129 //Get all attribute names from Descriptor
130 attributeIt = descriptor.getAttributeNames();
131
132 log.debug("addAll: Check if attributeIt is null : " + attributeIt);
133
134 boolean firstTime = true;
135
136 while (it.hasNext()) {
137 //Retrieving next row
138 currRow = it.next();
139
140 /**
141 * Create meta data section by analyzing the first data object's fields.
142 * We might want to consider storing this type of meta data in the descriptor
143 * so that this 'dynamic analysis' is not needed.
144 */
145
146 if (firstTime) {
147
148 //Loop through all attributes (columns) of first row and add to XML DOM
149 while (attributeIt.hasNext()) {
150 currAttrNm = (String)attributeIt.next();
151 currAttrVal = results.getAttribute(currAttrNm, currRow);
152
153 //if (currAttrVal != null) log.debug("addAll: meta-data: currAttrNm value: " + currAttrNm + "; currAttrVal value: " + currAttrVal + " instanceof: " + currAttrVal.getClass().getName());
154
155 //Dynamically determining XML data type for meta data section. Only done once for each column.
156 if (currAttrVal instanceof Number) {
157 currDType = "number";
158 }
159 else {
160 currDType = "text";
161 }
162
163 //Creating map of attribute names and values to be added to node
164 currElemAttribs = new HashMap();
165 currElemAttribs.put("name", currAttrNm);
166 currElemAttribs.put("dtype", currDType);
167 //Adding node to metadata section
168 addXMLNode(xmlDoc, metaData, "ColumnMetaData", currElemAttribs);
169 }
170
171 firstTime = false;
172 log.debug("addAll: Finished meta-data generation");
173 }
174
175
176 /**
177 * Now dealing with actual content
178 */
179
180 //Create new DataRow node.
181 HashMap typeDetail = new HashMap();
182 typeDetail.put("type", "detail");
183 Node dataRow = addXMLNode(xmlDoc, root, "DataRow", typeDetail);
184
185 //Get all attribute names from Descriptor
186 attributeIt = descriptor.getAttributeNames();
187 //Loop through all attributes (columns) of current row and add to XML DOM
188 while (attributeIt.hasNext()) {
189 currAttrNm = (String)attributeIt.next();
190 currAttrVal = results.getAttribute(currAttrNm, currRow);
191 if (currAttrVal != null) {
192 if (currAttrVal instanceof Number) {
193 currAttrVal = decFmt.format(currAttrVal); //Cut down decimals of numeric values to avoid confusion for the rendering XSL
194 }
195 else {
196 currAttrVal = currAttrVal.toString();
197 }
198 }
199 else currAttrVal = "";
200
201 //log.debug("addAll: content creation: currAttrNm value: " + currAttrNm + "; currAttrVal value: " + currAttrVal);
202
203 addXMLNode(xmlDoc, dataRow, "column", currAttrNm, (String)currAttrVal);
204 }
205
206 }
207
208 log.debug("addAll: After addall loop");
209
210 // By Nimish: Convert the XML DOM into a string representation so that it can be refered in a more memory efficient way.
211 ByteArrayOutputStream arrayOutputStream = null;
212 try {
213 XMLSerializer serializer = new XMLSerializer();
214 arrayOutputStream = new ByteArrayOutputStream();
215 serializer.setOutputByteStream(arrayOutputStream);
216 serializer.serialize(xmlDoc);
217 xmlString = arrayOutputStream.toString();
218 log.info("String length " + xmlString.length());
219 }
220 catch (Exception e) {
221 e.printStackTrace();
222 new CubeException(this, "addAll(Cube)", "Exception converting the XML DOM to String representation", e);
223 }
224 finally{
225 try
226 {arrayOutputStream.close();}
227 catch (Exception e){}
228
229 arrayOutputStream = null;
230 xmlDoc = null;
231 results = null;
232 }
233
234 /*
235 //Test output
236 try {
237 XMLSerializer serializer = new XMLSerializer();
238
239 //Setting the out put stream as System.out for the XML to be printed on the console. can give FileOutputStream for creating a file
240 File addAll = new File("/tmp/addALL.xml");
241 FileOutputStream fos = new FileOutputStream(addAll);
242 //serializer.setOutputByteStream(System.out);
243 serializer.setOutputByteStream(fos);
244 serializer.serialize(xmlDoc);
245 }
246 catch (Exception e) {
247 System.out.println("Exception in addALL " + e);
248 e.printStackTrace();
249 }
250 */
251
252 log.debug("addAll: Finished!");
253 }
254
255 /**
256 * Retrieve handle on internal XML document representation. Needed for getXML() method implementation
257 */
258 public Document getXmlDoc() {
259 return this.xmlDoc;
260 }
261
262 /**
263 * Retrieve handle on internal XML String representation. Needed for getXML() method implementation
264 */
265 public String getXmlString() {
266 return this.xmlString;
267 }
268
269
270 /*********** Internal helper methods ****************/
271
272
273 /**
274 * Helper method for adding elements to parent nodes
275 *
276 * @param xmlDoc XML DOM tree
277 * @param parent Parent node to attach this text node to
278 * @param elementName Element to be added
279 * @param attributes Map of attribute names mapped to attribute values
280 * @param value Value for text node
281 */
282 protected Node addXMLNode(Document xmlDoc, Node parent, String elementName, Map attributes, String value) {
283 Element node = xmlDoc.createElement(elementName);
284 if (attributes != null) {
285 Iterator it = attributes.keySet().iterator();
286 String currKey;
287 String currVal;
288 while (it.hasNext()) {
289 currKey = (String)it.next();
290 currVal = (String)attributes.get(currKey);
291 node.setAttribute(currKey, currVal);
292 }
293 }
294 if ((value != null) && (!value.equals(""))) {
295
296 //Removing forbidden characters (i.e. '<' and '>')
297 int forbiddenStringIndex;
298 while ((forbiddenStringIndex = value.indexOf("<")) != -1) {
299 value = value.substring(0, forbiddenStringIndex) + "<" + value.substring(forbiddenStringIndex+1);
300 }
301 while ((forbiddenStringIndex = value.indexOf(">")) != -1) {
302 value = value.substring(0, forbiddenStringIndex) + ">" + value.substring(forbiddenStringIndex+1);
303 }
304
305 node.appendChild(xmlDoc.createTextNode(value));
306 }
307 parent.appendChild(node);
308
309 return node;
310 }
311
312 /**
313 * Helper method for adding elements to parent nodes
314 *
315 * @param xmlDoc XML DOM tree
316 * @param parent Parent node to attach this text node to
317 * @param elementName Element to be added
318 * @param attributes Map of attribute names mapped to attribute values
319 */
320 protected Node addXMLNode(Document xmlDoc, Node parent, String elementName, Map attributes) {
321 return addXMLNode(xmlDoc, parent, elementName, attributes, null);
322 }
323
324 /**
325 * Helper method for adding elements to parent nodes
326 *
327 * @param xmlDoc XML DOM tree
328 * @param parent Parent node to attach this text node to
329 * @param elementName Element to be added
330 */
331 protected Node addXMLNode(Document xmlDoc, Node parent, String elementName) {
332 return addXMLNode(xmlDoc, parent, elementName, new String(), new String());
333 }
334
335 /**
336 * Helper method for adding elements to parent nodes
337 *
338 * @param xmlDoc XML DOM tree
339 * @param parent Parent node to attach this text node to
340 * @param elementName Element to be added
341 * @param nameAttribute Value for name attribute of element to be added
342 * @param value Value for text node*
343 */
344 protected Node addXMLNode(Document xmlDoc, Node parent, String elementName, String nameAttribute, String value) {
345 HashMap attributes = new HashMap();
346 if ((nameAttribute != null) && (!nameAttribute.equals(""))) {
347 attributes.put("name", nameAttribute);
348 }
349 return addXMLNode(xmlDoc, parent, elementName, attributes, value);
350 }
351
352 /**
353 * Helper method for adding elements to parent nodes
354 *
355 * @param xmlDoc XML DOM tree
356 * @param parent Parent node to attach this text node to
357 * @param elementName Element to be added
358 * @param nameAttribute Value for name attribute of element to be added
359 */
360 protected Node addXMLNode(Document xmlDoc, Node parent, String elementName, String nameAttribute) {
361 return addXMLNode(xmlDoc, parent, elementName, nameAttribute, "");
362 }
363
364 /*
365 public int size() {
366 NodeList list = xmlDoc.getElementsByTagName("DataRow");
367 return list.getLength();
368 }
369 */
370
371 public int size() {
372 return xmlString.length();
373 }
374
375 public CubeDescriptor getDescriptor() {
376 return descriptor;
377 }
378
379
380 /*********** Unsupported methods ***********/
381
382
383 /**
384 * Not currently supported by XML cubes
385 */
386 public Object getMax(String dimension) {
387 if (true) throw new UnsupportedCubeOperationException(this, "getMax()");
388 return null; //dummy return to satisfy compiler
389 }
390
391 /**
392 * Not currently supported by XML cubes
393 */
394 public Object getMin(String dimension) {
395 if (true) throw new UnsupportedCubeOperationException(this, "getMin()");
396 return null; //dummy return to satisfy compiler
397 }
398
399 /**
400 * Not currently supported by XML cubes
401 */
402 public double getSum(String attribute) {
403 if (true) throw new UnsupportedCubeOperationException(this, "getSum()");
404 return -1; //dummy return to satisfy compiler
405 }
406
407 /**
408 * Not currently supported by XML cubes
409 */
410 public Iterator groupBy(String attribute) {
411 if (true) throw new UnsupportedCubeOperationException(this, "groupBy()");
412 return null; //dummy return to satisfy compiler
413 }
414
415 /**
416 * Not currently supported by XML cubes
417 */
418 public Object getAttribute(String attribute, Object object) {
419 if (true) throw new UnsupportedCubeOperationException(this, "getAttribute()");
420 return null; //dummy return to satisfy compiler
421 }
422
423 /**
424 * Not currently supported by XML cubes
425 */
426 public SortedSet getAttributeSet(String attribute) {
427 if (true) throw new UnsupportedCubeOperationException(this, "getAttributeSet()");
428 return null; //dummy return to satisfy compiler
429 }
430
431 /**
432 * Not currently supported by XML cubes
433 */
434 public Iterator iterator() {
435 throw new UnsupportedCubeOperationException(this, "iterator()");
436 }
437
438 /**
439 * Not currently supported by XML cubes
440 */
441 public boolean add(Object object) {
442 if (true) throw new UnsupportedCubeOperationException(this, "add()");
443 return false; //dummy return to satisfy compiler
444 }
445
446 }