Source code: org/jdaemon/era/xml/GenericTotal.java
1 /*
2 * GenericTotal.java
3 *
4 * Copyright (C) 2002 Nimish Shah
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 August 2002
9 */
10
11 package org.jdaemon.era.xml;
12
13 import org.apache.xerces.parsers.*;
14 import org.apache.xml.serialize.*;
15 import org.xml.sax.*;
16 import org.w3c.dom.*;
17
18 import java.io.*;
19 import java.text.*;
20 import java.util.*;
21
22 import org.jdaemon.era.CubeDescriptor;
23 import org.jdaemon.util.AttributeList;
24
25 //Log4J
26 import org.apache.log4j.Logger;
27
28 /**
29 * This is a helper class used for inserting totals after groups breaks. This class is used by XMLCube for totaling non numeric fields for
30 * each report. The group breaks and columns which need to be totaled are passed in as an argument. The class accepts a Document object for XML
31 * and arrays for group list and total columns. The first group should be the first element in the groupby arrray
32 *
33 * @author Nimish Shah
34 */
35 public class GenericTotal {
36
37 /** Logger */
38 private static Logger log = Logger.getLogger("--GenericTotal--");
39
40 /**
41 * The format object used to convert the exponent value representation into a normal number.
42 */
43 DecimalFormat format = new DecimalFormat("########0.00");
44
45 Set[] totalonLevelDependent;
46
47 /**
48 * This is a no argument constructor for the class
49 */
50 public GenericTotal() {}
51
52 /**
53 * This method can be used to test this class from command prompt. The XML file needs to be argument passed to this class. The
54 * groupby and total columns are hardcoded.
55 */
56
57 public static void main(String[] args) {
58 try {
59 long startTime = System.currentTimeMillis();
60
61 String[] groupby = {"AccountTypeNarrative", "AccountNumber", "LocalCCY"};
62 String[] totalafter = {"true", "false", "true"};
63 String[] totalon = {"MarketValueBase", "MarketValueLocal"};
64 GenericTotal genericTotal = new GenericTotal();
65
66 //Convert the xml file in to input source
67 InputSource source = new InputSource(args[0]);
68 //create a new DOM Parser
69 DOMParser parser = new DOMParser();
70 //Parse the xml file
71 parser.parse(source);
72 //get the document object
73 Document doc = parser.getDocument();
74
75 doc = genericTotal.insertTotals(doc, groupby, totalafter, totalon, null);
76
77 log.debug("Total time from main method is " + (System.currentTimeMillis()- startTime));
78 }
79 catch (Exception e) {
80 e.printStackTrace();
81 }
82 }
83
84 /**
85 * This method is called on the reuqest of client. This method accepts the request from client and calls
86 * different methods and processes the request. This method delegates the request to handleRequest,
87 * which internally delegates it to the corresponding handler as per the Source value. The referece of
88 * handler class is obtained from the Action mapping class. After getting the processing done from
89 * hander class. The request is dispatched to the corresponding url for action value (display type).
90 * The url is obtained from the Action Mapping class
91 * @param Document doc -- The document which needs to be processd for adding total rows.
92 * @param String[] groupby -- The array of group break columns
93 * @param String[] totalon -- The array of columns which need to be totaled on.
94 * @param Map attributeMetadataMap -- Map containing meta data of Cub
95 */
96
97 public Document insertTotals(Document doc, String[] groupby, String[] totalafter, String[] totalon, Map attributeMetadataMap /*, CubeDescriptor descriptor*/) {
98
99 //Removing '_Abs' or '_Ms' from groupbreak name to prevent wrong totals label. This is not final but will have
100 //to suffice until we have put the meta data into the Cube that would link hidden and display columns to one another.
101 for (int i=0; i < groupby.length; i++) {
102 String currElement = groupby[i];
103
104 log.debug("insertTotals: currElement is: " + currElement);
105
106 if (currElement.endsWith("_Abs") || currElement.endsWith("_Ms")) {
107 int index = currElement.indexOf("_Abs");
108 if (index == -1) index = currElement.indexOf("_Ms");
109 groupby[i] = currElement.substring(0, index);
110 }
111
112 log.debug("insertTotals: modified currElement is: " + groupby[i]);
113 }
114
115 /**
116 * Setting up the arrays for holding the current and previous values of group break columns. Also defining as array
117 * to hold the totals for different columns of different group breaks.
118 */
119 int groupbreaks = groupby.length;
120 int nooftotals = totalon.length;
121
122 if (groupbreaks == 0 || nooftotals == 0)
123 {return doc;}
124
125 String[] tmp_groupby = {"none", "none", "none"};
126 String[] tmp_totalafter = {"false", "false", "false"};
127
128 log.debug("Total no of groups breaks are : " + groupbreaks);
129
130 if (groupbreaks >= 3) {
131 for (int z=0;z < 3 ;z++ ) {
132 tmp_groupby[z] = groupby[z];
133 tmp_totalafter[z] = totalafter[z];
134 log.debug(z + " : Show total " + totalafter[z] + " for Group break " + groupby[z]);
135 }
136 groupbreaks = 3;
137 }
138 else {
139 for (int z=0; z < groupbreaks; z++) {
140 tmp_groupby[z] = groupby[z];
141 tmp_totalafter[z] = totalafter[z];
142 log.debug(z + " : Show total " + totalafter[z] + " for Group break " + groupby[z]);
143 }
144 }
145
146
147
148 // Creating new totalonMasterSet based on original totalon array values.
149 // BUT: All columns that should not be totaled because the field they
150 // are dependent on is not in the groupby array are not being added to
151 // that totalonMasterSet set.
152
153 HashSet totalonMasterSet = new HashSet();
154
155 //Looping through all totalon columns
156 for (int z = 0; z < totalon.length; z++) {
157 boolean keyFound = false;
158
159 //Safety check in case the links map is null (for cubes that did not create it)
160 if (attributeMetadataMap != null) {
161
162 log.debug("insertTotals: attributeMetadataMap is not null");
163
164 //Looping through all keys of links map to see if the current columns is contained
165 //in that key's values
166 Set keySet = attributeMetadataMap.keySet();
167 Iterator it = keySet.iterator();
168 while (it.hasNext()) {
169 String currKey = (String)it.next();
170
171 log.debug("insertTotals: Looping through attributeMetadataMap: currKey is " + currKey);
172
173 Object attrListObj = attributeMetadataMap.get(currKey);
174 if (attrListObj == null) {
175 log.debug("insertTotals:attrListObj is null");
176 continue;
177 }
178 Object totalDepends = ((AttributeList)attrListObj).get("total-dependency");
179 if (totalDepends == null) {
180 log.debug("insertTotals: totalDepends is null");
181 continue;
182 }
183
184 log.debug("insertTotals: totalDepends is " + totalDepends);
185
186 String[] currLinks = (String[])totalDepends;
187 Arrays.sort(currLinks);
188 int result = Arrays.binarySearch(currLinks, totalon[z]);
189 if (result >= 0) { //=found key, now check if key in list of groupby
190 keyFound = true;
191 for (int j = 0; j < tmp_groupby.length; j++) {
192 if (tmp_groupby[j].equals(currKey)) {
193 log.debug("insertTotals: Adding to totalonMasterSet: " + totalon[z]);
194 //Add column if one of the groupbys equals the current key
195 totalonMasterSet.add(totalon[z]);
196 break;
197 }
198 }
199 }
200 }
201 }
202
203 if (!keyFound) {
204 totalonMasterSet.add(totalon[z]);
205 log.debug("insertTotals: (else) Adding " + totalon[z] + " to totalonMasterSet");
206 }
207
208 }
209
210
211 // Now creating Set that holds the column sets for all group levels
212
213 this.totalonLevelDependent = new HashSet[tmp_groupby.length];
214
215 String[] currLinks = null;
216
217 //For every group level, figure out what column totals to display
218 for (int k = tmp_groupby.length-1; k >= 0; k--) {
219 log.debug("insertTotals: (Iteration " + k + ") In loop for totalonMasterSet");
220
221 //If currLinks is not null (due to previous iteration), remove the contained
222 //columns from the set of totaled columns
223 if (currLinks != null) {
224 for (int h = 0; h < currLinks.length; h++) {
225 //For next level, remove the dependent columns
226 totalonMasterSet.remove(currLinks[h]);
227 log.debug("insertTotals: (Iteration " + k + ") Removing column from master list: " + currLinks[h]);
228 }
229 }
230 //Define the set of level-k totaling columns as clone of the 'totalonMasterSet'. The master set
231 //may be further reduced in the next iteration for the next sorting level.
232 totalonLevelDependent[k] = (Set)totalonMasterSet.clone();
233
234 log.debug("insertTotals: (Iteration " + k + ") After cloning of totalonMasterSet");
235
236 //Retrieve dependent columns for next sorting level (if any)
237 if (attributeMetadataMap != null) {
238 Object attrListObj = attributeMetadataMap.get(tmp_groupby[k]);
239 if (attrListObj == null) {
240 log.debug("insertTotals: (Iteration " + k + ") attrListObj (2) is null");
241 }
242 else {
243 log.debug("insertTotals: (Iteration " + k + ") attrListObj (2) is NOT null");
244 Object totalDepends = ((org.jdaemon.util.AttributeList)attrListObj).get("total-dependency");
245 if (totalDepends != null) {
246 log.debug("insertTotals: (Iteration " + k + ") totalDepends (2) is null");
247 currLinks = (String[])totalDepends;
248 }
249 }
250 }
251
252 log.debug("insertTotals: At end of loop for totalonMasterSet");
253 }
254
255 log.debug("insertTotals: After loop for totalonMasterSet");
256
257
258
259 // Now doing the actual totaling and adding of total rows to XML
260 // document structure
261
262 String[] prevGroup = new String[groupbreaks];
263 String[] currGroup = new String[groupbreaks];
264 double[][] totals = new double[groupbreaks][nooftotals];
265
266 //Creating an array to hold current value of the columns which need to be totaled
267 double[] curvalue1 = new double[nooftotals];
268
269 try {
270 //get the total no DataRow's present in the XML document
271
272 NodeList nodeList = doc.getElementsByTagName("DataRow");
273 int nodelength = nodeList.getLength();
274
275 //Logic for Totals need to be processed only if there is atleast one DataRow present in the XML
276 if (nodelength > 0) {
277 //Get root node DataSet from the document
278 Node dataSet = doc.getFirstChild();
279
280 //Take the pointer to node before DataRow. This will be text node of ResultSetMetaData
281 Node node = dataSet.getFirstChild();
282 node = node.getNextSibling();
283 node = node.getNextSibling();
284
285 //Traverse the XML document for all the DataRow's
286 for (int i = 0; i < nodelength; i++) {
287 node = node.getNextSibling();
288
289 log.debug("insertTotals: After getNextSibling, node is: " + node);
290
291 //get the current values for the group break columns
292 for (int j=0; j<groupbreaks; j++) {
293 if (tmp_groupby[j].equals("none") || !(tmp_totalafter[j].equals("true")) ) {
294 currGroup[j] = "none";
295 }
296 else {
297 NodeList childNodeList = ((Element)node).getElementsByTagName("column");
298 int colCount = childNodeList.getLength();
299 for (int z=0; z < colCount; z++){
300 Attr attribute = ((Element)childNodeList.item(z)).getAttributeNode("name");
301 if (attribute.getValue().equals(tmp_groupby[j])) {
302 Node child = childNodeList.item(z).getFirstChild();
303 if (child == null)
304 {currGroup[j] = "";}
305 else
306 {currGroup[j] = child.getNodeValue().trim();}
307 break;
308 }
309 }
310 }
311 }
312
313 //get the current values for the columns which need to be totaled
314 for (int j=0; j<nooftotals; j++) {
315 NodeList childNodeList = ((Element)node).getElementsByTagName("column");
316 int colCount = childNodeList.getLength();
317 for (int z=0; z < colCount; z++){
318 Attr attribute = ((Element)childNodeList.item(z)).getAttributeNode("name");
319 if (attribute.getValue().equals(totalon[j])) {
320 Node child = childNodeList.item(z).getFirstChild();
321 curvalue1[j] = Double.valueOf(child.getNodeValue().trim()).doubleValue();
322 break;
323 }
324 }
325 }
326
327 /**
328 * Check for the first group level if the previous group break value is same as current then keep adding the
329 * data for total else insert a totals column in output XML Document.
330 */
331 if (prevGroup[0] == null || prevGroup[0].equals(currGroup[0])) {
332
333 for (int z=0; z<nooftotals; z++)
334 {totals[0][z] = totals[0][z] + curvalue1[z];}
335
336 /**
337 * Check for the second group level if the previous group break value is same as current then keep adding the
338 * data for total else insert a totals column in output XML Document.
339 */
340 if (prevGroup[1] == null || prevGroup[1].equals(currGroup[1])) {
341 for (int z=0; z<nooftotals; z++)
342 {totals[1][z] = totals[1][z] + curvalue1[z];}
343
344 /**
345 * Check for the first group level if the previous group break value is same as current then keep adding the
346 * data for total else insert a totals column in output XML Document.
347 */
348 if (prevGroup[2] == null || prevGroup[2].equals(currGroup[2])) {
349 for (int z=0; z<nooftotals; z++)
350 {totals[2][z] = totals[2][z] + curvalue1[z];}
351 }
352 else {
353 //Insert total rows for third group break
354 if (tmp_totalafter[2].equals("true"))
355 {insertTotalsRow(doc, dataSet, node, totalon, totals[2], "2", prevGroup[2]);}
356 else
357 {insertTotalsRow(doc, dataSet, node, totalon, totals[2], "2", "none");}
358
359 //reset the total to current value
360 for (int z=0; z<nooftotals; z++)
361 {totals[2][z] = curvalue1[z];}
362 }
363 }
364 else {
365 //Insert total rows for second and third group break
366 if (tmp_totalafter[2].equals("true"))
367 {insertTotalsRow(doc, dataSet, node, totalon, totals[2], "2", prevGroup[2]);}
368 else
369 {insertTotalsRow(doc, dataSet, node, totalon, totals[2], "2", "none");}
370 if (tmp_totalafter[1].equals("true"))
371 {insertTotalsRow(doc, dataSet, node, totalon, totals[1], "1", prevGroup[1]);}
372 else
373 {insertTotalsRow(doc, dataSet, node, totalon, totals[1], "1", "none");}
374
375 //reset the totals to current value
376 for (int z=0; z<nooftotals; z++)
377 {totals[1][z] = curvalue1[z];}
378 for (int z=0; z<nooftotals; z++)
379 {totals[2][z] = curvalue1[z];}
380 }
381 }
382 else {
383 //Insert total rows for first, second and third group break
384 if (tmp_totalafter[2].equals("true"))
385 {insertTotalsRow(doc, dataSet, node, totalon, totals[2], "2", prevGroup[2]);}
386 else
387 {insertTotalsRow(doc, dataSet, node, totalon, totals[2], "2", "none");}
388 if (tmp_totalafter[1].equals("true"))
389 {insertTotalsRow(doc, dataSet, node, totalon, totals[1], "1", prevGroup[1]);}
390 else
391 {insertTotalsRow(doc, dataSet, node, totalon, totals[1], "1", "none");}
392 if (tmp_totalafter[0].equals("true"))
393 {insertTotalsRow(doc, dataSet, node, totalon, totals[0], "0", prevGroup[0]);}
394 else
395 {insertTotalsRow(doc, dataSet, node, totalon, totals[0], "0", "none");}
396
397 //reset the totals to current value
398 for (int z=0; z<nooftotals; z++)
399 {totals[0][z] = curvalue1[z];}
400 for (int z=0; z<nooftotals; z++)
401 {totals[1][z] = curvalue1[z];}
402 for (int z=0; z<nooftotals; z++)
403 {totals[2][z] = curvalue1[z];}
404 }
405
406 //assigning the current values for group break columns as previous values before starting next row
407 for (int j =0; j < groupbreaks; j++)
408 {prevGroup[j] = currGroup[j];}
409
410 node = node.getNextSibling();
411 }
412
413 //Insert total rows after last dataRow.
414 if (tmp_totalafter[2].equals("true"))
415 {insertTotalsRow(doc, dataSet, node, totalon, totals[2], "2", prevGroup[2]);}
416 else
417 {insertTotalsRow(doc, dataSet, node, totalon, totals[2], "2", "none");}
418 if (tmp_totalafter[1].equals("true"))
419 {insertTotalsRow(doc, dataSet, node, totalon, totals[1], "1", prevGroup[1]);}
420 else
421 {insertTotalsRow(doc, dataSet, node, totalon, totals[1], "1", "none");}
422 if (tmp_totalafter[0].equals("true"))
423 {insertTotalsRow(doc, dataSet, node, totalon, totals[0], "0", prevGroup[0]);}
424 else
425 {insertTotalsRow(doc, dataSet, node, totalon, totals[0], "0", "none");}
426 /**
427 File xmloutput = new File("/tmp/xmloutput.xml");
428 FileOutputStream fos = new FileOutputStream(xmloutput);
429 XMLSerializer serializer = new XMLSerializer();
430 serializer.setOutputByteStream(fos);
431 serializer.serialize(doc);
432 fos.close();
433 */
434 }
435 } catch (Exception e) {
436 log.error("There is an exception in GenericTotal class " + e);
437 // StringWriter will contain text of stack trace
438 StringWriter stringWriter = new StringWriter();
439 // Need to encapsulate the StringWriter into a Printwriter object to fill up with stack trace
440 PrintWriter printWriter = new PrintWriter(stringWriter);
441 // Get the stack trace and fill the PrintWriter
442 e.printStackTrace(printWriter);
443 // StringBuffer to hold stack trace
444 StringBuffer error = stringWriter.getBuffer();
445 // Close writers
446 printWriter.close();
447 //Print error
448 log.error(error.toString());
449 e.printStackTrace();
450 }
451 return doc;
452 }
453
454 /**
455 * This method is private to this class. It is internally used to append the totals row inside the XML Document. All the
456 * data required to create the DataRow is passed as an argument.
457 * param Document doc -- The XML document which is being processed
458 * param Node rootNode -- The root node of the document which is getting processed
459 * param Node refNode -- The Element before which the totals row needs to be inserted
460 * param String[] colname -- The column names for which the total is begin added
461 * param String[] data -- The total data
462 * param String grouplevel -- The grouping level for which the total is inserted.
463 */
464 private void insertTotalsRow(Document doc, Node rootNode, Node refNode, String[] colname, double[] data, String grouplevel, String groupbreak) {
465 Element dataRow = doc.createElement("DataRow");
466 dataRow.setAttribute("type", "total");
467
468 Element groupLevel = doc.createElement("grouplevel");
469 groupLevel.appendChild(doc.createTextNode(grouplevel));
470 dataRow.appendChild(groupLevel);
471
472 Element groupBreak = doc.createElement("groupbreak");
473 groupBreak.appendChild(doc.createTextNode(groupbreak));
474 dataRow.appendChild(groupBreak);
475
476 Set totalonLevelDependent = this.totalonLevelDependent[Integer.parseInt(grouplevel)];
477
478 for (int z=0; z<colname.length; z++) {
479 String currCol = colname[z];
480
481 log.debug("insertTotalsRows: Inserting total for column " + currCol + ": " + data[z]);
482
483 if (!totalonLevelDependent.contains(currCol))
484 continue;
485
486 Element total = doc.createElement("total");
487
488 Element colName = doc.createElement("colname");
489 colName.appendChild(doc.createTextNode(currCol));
490 total.appendChild(colName);
491
492 Element dataTotal = doc.createElement("data");
493 dataTotal.appendChild(doc.createTextNode(format.format(data[z])));
494 total.appendChild(dataTotal);
495
496 dataRow.appendChild(total);
497 }
498 rootNode.insertBefore(dataRow, refNode);
499 }
500
501 }