KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > tonbeller > jpivot > mondrian > MondrianModel


1 /*
2  * ====================================================================
3  * This software is subject to the terms of the Common Public License
4  * Agreement, available at the following URL:
5  * http://www.opensource.org/licenses/cpl.html .
6  * Copyright (C) 2003-2004 TONBELLER AG.
7  * All Rights Reserved.
8  * You must accept the terms of that agreement to use this software.
9  * ====================================================================
10  *
11  *
12  */

13
14 package com.tonbeller.jpivot.mondrian;
15
16 import java.net.URI JavaDoc;
17 import java.net.URISyntaxException JavaDoc;
18 import java.util.ArrayList JavaDoc;
19 import java.util.LinkedList JavaDoc;
20 import java.util.Collection JavaDoc;
21 import java.util.HashMap JavaDoc;
22 import java.util.HashSet JavaDoc;
23 import java.util.Iterator JavaDoc;
24 import java.util.List JavaDoc;
25 import java.util.Locale JavaDoc;
26 import java.util.Map JavaDoc;
27 import java.util.Set JavaDoc;
28
29 import javax.servlet.ServletContext JavaDoc;
30 import javax.sql.DataSource JavaDoc;
31
32 import mondrian.mdx.DimensionExpr;
33 import mondrian.mdx.HierarchyExpr;
34 import mondrian.mdx.LevelExpr;
35 import mondrian.mdx.MdxVisitorImpl;
36 import mondrian.mdx.MemberExpr;
37 import mondrian.mdx.ParameterExpr;
38 import mondrian.mdx.UnresolvedFunCall;
39 import mondrian.olap.Category;
40 import mondrian.olap.Cube;
41 import mondrian.olap.Exp;
42 import mondrian.olap.Formula;
43 import mondrian.olap.FunCall;
44 import mondrian.olap.Literal;
45 import mondrian.olap.MondrianException;
46 import mondrian.olap.Parameter;
47 import mondrian.olap.ParameterImpl;
48 import mondrian.olap.Query;
49 import mondrian.olap.QueryAxis;
50 import mondrian.olap.Role;
51 import mondrian.olap.SchemaReader;
52 import mondrian.olap.Syntax;
53 import mondrian.olap.Util;
54 import mondrian.olap.type.NumericType;
55 import mondrian.olap.type.Type;
56 import mondrian.olap.ResultLimitExceededException;
57 import mondrian.olap.MemoryLimitExceededException;
58 import mondrian.rolap.RolapConnection;
59 import mondrian.rolap.RolapConnectionProperties;
60 import mondrian.spi.CatalogLocator;
61 import mondrian.spi.impl.ServletContextCatalogLocator;
62 import mondrian.util.MemoryMonitor;
63 import mondrian.util.MemoryMonitorFactory;
64
65 import org.apache.log4j.Logger;
66
67 import com.tonbeller.jpivot.core.Extension;
68 import com.tonbeller.jpivot.core.ModelChangeEvent;
69 import com.tonbeller.jpivot.core.ModelChangeListener;
70 import com.tonbeller.jpivot.olap.model.Dimension;
71 import com.tonbeller.jpivot.olap.model.Member;
72 import com.tonbeller.jpivot.olap.model.OlapException;
73 import com.tonbeller.jpivot.olap.model.OlapModel;
74 import com.tonbeller.jpivot.olap.model.Result;
75 import com.tonbeller.jpivot.olap.navi.SortRank;
76 import com.tonbeller.jpivot.olap.query.ExpBean;
77 import com.tonbeller.jpivot.olap.query.MdxOlapModel;
78 import com.tonbeller.jpivot.olap.query.Memento;
79 import com.tonbeller.jpivot.olap.query.PositionNodeBean;
80 import com.tonbeller.jpivot.olap.query.QueryAdapter;
81 import com.tonbeller.wcf.bookmarks.Bookmarkable;
82
83 /**
84  * The Model represents all (meta-)data for an MDX query.
85  */

86 public class MondrianModel extends MdxOlapModel implements OlapModel,
87     QueryAdapter.QueryAdapterHolder {
88
89   static Logger logger = Logger.getLogger(MondrianModel.class);
90
91   static final String JavaDoc LOGICAL_MDX_PROP = "com.tonbeller.jpivot.mondrian.logical.mdx";
92
93   /*
94    * sample value
95    * provider=Mondrian;Jdbc=jdbc:odbc:MondrianFoodMart;Catalog=file:///c:/dev/mondrian/demo/FoodMart.xml
96    */

97   private String JavaDoc connectString = null;
98   private Util.PropertyList connectProperties = null;
99
100   /*
101    * sample values sun.jdbc.odbc.JdbcOdbcDriver com.mysql.jdbc.Driver
102    */

103   private String JavaDoc jdbcDriver = null;
104   private mondrian.olap.Connection monConnection = null;
105
106   /**
107    * the initial MDX query. This is never changed except when the user enters a new MDX query.
108    */

109   private String JavaDoc mdxQuery;
110
111   private String JavaDoc currentMdx;
112   private MondrianResult result = null;
113   private HashMap JavaDoc hDimensions = new HashMap JavaDoc();
114   private HashMap JavaDoc hHierarchies = new HashMap JavaDoc();
115   private HashMap JavaDoc hLevels = new HashMap JavaDoc();
116   private HashMap JavaDoc hMembers = new HashMap JavaDoc();
117   private ArrayList JavaDoc aMeasures = new ArrayList JavaDoc();
118
119   private List JavaDoc aLogicalModel = new LinkedList JavaDoc();
120
121   private MondrianQueryAdapter queryAdapter = null;
122   private Listener listener = null;
123
124   private boolean isInitialized = false;
125   private String JavaDoc ID = null;
126   private Locale JavaDoc loc = null;
127
128   private String JavaDoc sessionId = null;
129   private String JavaDoc dynresolver = null;
130
131   // selected locale to be used by dynResolver (if given)
132
private String JavaDoc dynLocale = null;;
133
134   private boolean connectionPooling = true; // Mondrian connection Pooling
135

136   private DataSource JavaDoc externalDataSource = null;
137
138   private ServletContext JavaDoc servletContext = null;
139
140   private Object JavaDoc bookMark = null;
141
142   private String JavaDoc dataSourceChangeListener = null;
143
144   public String JavaDoc getID() {
145     return ID;
146   }
147
148   
149   /**
150    * check for OutOfMemory
151    */

152   final void checkListener() throws MemoryLimitExceededException {
153     if (this.listener != null) {
154       this.listener.check();
155     }
156   }
157
158   public void setID(String JavaDoc ID) {
159     this.ID = ID;
160   }
161
162   /**
163    * constructor must be "default"
164    */

165   public MondrianModel() {
166     this.mdxQuery = null;
167     this.currentMdx = null;
168
169     addModelChangeListener(new ModelChangeListener() {
170       public void modelChanged(ModelChangeEvent e) {
171         result = null; // will force re-execution of query
172
}
173
174       public void structureChanged(ModelChangeEvent e) {
175         result = null; // will force re-execution of query
176
}
177     });
178
179   }
180
181   /**
182    * Returns the queryAdapter.
183    *
184    * @return MondrianQueryAdapter
185    */

186   public QueryAdapter getQueryAdapter() {
187     return queryAdapter;
188   }
189
190   /**
191    * Let Mondrian parse and execute the query
192    *
193    * @see com.tonbeller.jpivot.olap.model.OlapModel#getResult()
194    * @return Result of Query Execution
195    */

196   public synchronized Result getResult() throws OlapException {
197
198     if (result != null) {
199       return result;
200     }
201
202     if (!isInitialized) {
203       throw new OlapException("Model not initialized");
204     }
205
206     this.listener = new Listener();
207     MemoryMonitor mm = MemoryMonitorFactory.getMemoryMonitor();
208     try {
209       mm.addListener(this.listener);
210
211       queryAdapter.onExecute();
212
213       mondrian.olap.Result monResult = null;
214       boolean tryagain = false;
215
216       try {
217         String JavaDoc mdx = null;
218         if (Boolean.getBoolean(LOGICAL_MDX_PROP)) {
219           mondrian.olap.Query modiQuery = rewriteMDXQuery(queryAdapter.getMonQuery());
220           queryAdapter.setMonQuery(modiQuery);
221           mdx = queryAdapter.getMonQuery().toString();
222           setCurrentMdx(mdx);
223         }
224         if (logger.isDebugEnabled()) {
225            if (mdx == null) {
226               mdx = queryAdapter.getMonQuery().toString();
227            }
228            logger.debug(mdx);
229         }
230         // check for OutOfMemory
231
this.listener.check();
232   
233         long t1 = System.currentTimeMillis();
234         monResult = monConnection.execute(queryAdapter.getMonQuery());
235
236         // check for OutOfMemory
237
this.listener.check();
238   
239         if (logger.isInfoEnabled()) {
240           long t2 = System.currentTimeMillis();
241           logger.info("query execution time " + (t2 - t1) + " ms");
242         }
243   
244       } catch (MondrianException ex) {
245         Throwable JavaDoc rootCause = getRootCause(ex);
246         if (rootCause instanceof ResultLimitExceededException) {
247           // the result limit was exceeded - roll back
248
logger.warn("Mondrian result limit exceeded: " + rootCause.getMessage());
249           if (bookMark != null) {
250             setBookmarkState(bookMark);
251             tryagain = true;
252           }
253         } else if (rootCause instanceof mondrian.olap.InvalidHierarchyException) {
254           // there was no member for an hierarchy
255
logger.warn("Mondrian Hierarchy with no members: " +
256                   rootCause.getMessage());
257           throw new EmptyCubeException(rootCause);
258         } else {
259           // the cause of the exception is different from "Result Limit Exceeded"
260
throw new OlapException(ex);
261         }
262   
263         if (!tryagain) {
264           throw new ResultTooLargeException(ex);
265         }
266       }
267       if (tryagain) {
268         // roll back to bookmark occurred
269
// a Result Limit Overflow will not occur here
270
try {
271           long t1 = System.currentTimeMillis();
272           monResult = monConnection.execute(queryAdapter.getMonQuery());
273           // check for OutOfMemory
274
this.listener.check();
275   
276           if (logger.isInfoEnabled()) {
277             long t2 = System.currentTimeMillis();
278             logger.info("rollback query execution time " + (t2 - t1) + " ms");
279           }
280   
281         } catch (MondrianException ex) {
282           // should not occur
283
// throw ResultTooLargeException because this was the original problem
284
throw new ResultTooLargeException(
285               "Error running previous query (prior to Result Overflow)", ex);
286         }
287       }
288       result = new MondrianResult(monResult, this);
289       if (tryagain) {
290         result.setOverflowOccured(true);
291       }
292   
293       queryAdapter.afterExecute(result);
294   
295       // set a bookmark, so that we can roll back to that state
296
if (!tryagain) {
297         bookMark = getBookmarkState(EXTENSIONAL);
298       }
299     } finally {
300       mm.removeListener(this.listener);
301       this.listener = null;
302     }
303
304     return result;
305   }
306
307   /**
308    * get the result variable without any action
309    *
310    * @return current Mondrian result, or null
311    */

312   MondrianResult currentResult() {
313     return result;
314   }
315
316   /**
317    * @see com.tonbeller.jpivot.olap.model.OlapModel#getDimensions()
318    */

319   public Dimension[] getDimensions() {
320     return (Dimension[]) hDimensions.values().toArray(new Dimension[0]);
321   }
322
323   /**
324    * @see com.tonbeller.jpivot.olap.model.OlapModel#getMeasures()
325    */

326   public Member[] getMeasures() {
327     return (Member[]) aMeasures.toArray(new Member[0]);
328   }
329
330   /**
331    * set the Mondrian Connect String
332    *
333    * @param connectString
334    * Connect String - default:
335    * provider=Mondrian;Jdbc=jdbc:odbc:MondrianFoodMart;
336    * Catalog=file:///c:/j/mondrian/demo/FoodMart.xml
337    */

338   public void setConnectString(String JavaDoc connectString) {
339     this.connectString = connectString;
340     result = null;
341     queryAdapter = null;
342     monConnection = null;
343     if (logger.isInfoEnabled())
344       logger.info("connectString=" + connectString);
345   }
346
347   /**
348    * set the Mondrian Connection Properties
349    * as an alternative to setConnectString
350    *
351    * @param properties
352    */

353   public void setConnectProperties(Util.PropertyList properties) {
354     connectProperties = properties;
355     result = null;
356     queryAdapter = null;
357     monConnection = null;
358     if (logger.isInfoEnabled())
359       logger.info("connectProperties=" + connectProperties);
360   }
361
362
363   /**
364    * set the JDBC Driver
365    *
366    * @param jdbcDriver
367    * JDBC Driver - default: sun.jdbc.odbc.JdbcOdbcDriver
368    */

369   public void setJdbcDriver(String JavaDoc jdbcDriver) {
370     this.jdbcDriver = jdbcDriver;
371     result = null;
372     queryAdapter = null;
373     monConnection = null;
374     if (logger.isInfoEnabled())
375       logger.info("jdbcDriver=" + jdbcDriver);
376   }
377
378   /**
379    * Sets the mdxQuery.
380    *
381    * @param mdxQuery
382    * The mdxQuery to set
383    */

384   public void setMdxQuery(String JavaDoc mdxQuery) {
385
386     if (logger.isInfoEnabled())
387       logger.info("setMdxQuery:" + mdxQuery);
388
389     this.mdxQuery = mdxQuery;
390     this.currentMdx = mdxQuery.replaceAll("\r", "");
391     result = null;
392     queryAdapter = null;
393   }
394
395   /**
396    * complete the initilization.
397    */

398   public void initialize() throws OlapException {
399     logger.info(this);
400     boolean logInfo = logger.isInfoEnabled();
401
402     // load the jdbc Driver
403
if (jdbcDriver != null) {
404       try {
405         Class.forName(jdbcDriver);
406       } catch (Exception JavaDoc ex) {
407         String JavaDoc err = "Could not load Jdbc Driver " + jdbcDriver;
408         logger.error(err);
409         throw new OlapException(err);
410       }
411     }
412
413     Util.PropertyList properties = getConnectProperties();
414
415     boolean updatedProperties = false;
416
417     if (properties == null) {
418       properties = Util.parseConnectString(connectString);
419       updatedProperties = true;
420     }
421
422     // get the Catalog from connect string
423
String JavaDoc catString = properties.get("Catalog");
424     URI JavaDoc uri = null;
425     try {
426       if (catString != null) {
427         uri = new URI JavaDoc(catString);
428       }
429     } catch (URISyntaxException JavaDoc e) {
430       //throw new IllegalArgumentException("Illegal Schema Url " + catString );
431
// ignore;
432
}
433
434     if (uri != null && uri.getScheme().equalsIgnoreCase("http") && sessionId != null) {
435       // an http schema url will be dynamically resolved
436
// in that case, a session id has to be appended
437
if (uri.getQuery() != null) {
438         catString = catString + "&sessionId=" + sessionId;
439       } else {
440         catString = catString + "?sessionId=" + sessionId;
441       }
442       properties.put(RolapConnectionProperties.Catalog.name(), catString);
443       updatedProperties = true;
444     }
445
446     if (dynresolver != null && dynresolver.length() > 0) {
447       properties.put(RolapConnectionProperties.DynamicSchemaProcessor.name(), dynresolver);
448       updatedProperties = true;
449     }
450     if (dynLocale!=null) {
451       properties.put(RolapConnectionProperties.Locale.name(), dynLocale);
452       updatedProperties = true;
453     }
454     if (dataSourceChangeListener != null && dataSourceChangeListener.length() > 0) {
455         properties.put(RolapConnectionProperties.DataSourceChangeListener.name(), dataSourceChangeListener);
456           updatedProperties = true;
457       }
458
459     // if we do *not* want connection pooling, we must explicitly tell Mondrian
460
if (!connectionPooling) {
461       properties.put(RolapConnectionProperties.PoolNeeded.name(), "false");
462       updatedProperties = true;
463     }
464
465     if (updatedProperties) {
466       setConnectProperties(properties);
467     }
468
469     CatalogLocator catalogLocator = new ServletContextCatalogLocator(servletContext);
470
471     // use external DataSource if present
472
monConnection = mondrian.olap.DriverManager.getConnection(properties, catalogLocator, externalDataSource, false);
473
474     if (monConnection == null) {
475       String JavaDoc err = "Could not create Mondrian connection:" + properties;
476       logger.error(err);
477       throw new OlapException(err);
478     }
479     if (logInfo)
480       logger.info("MondrianModel: opening connection " + properties);
481
482     // do we have a special locale setting?
483
// if yes, promote it to the connection
484
loc = getLocale(); // Locale.GERMANY
485
if (loc != null) {
486       if (logInfo) {
487         String JavaDoc msg = "Locale language=" + loc.getLanguage() + " Country=" + loc.getCountry();
488         logger.info(msg);
489       }
490       ((RolapConnection) monConnection).setLocale(loc);
491     }
492
493     mondrian.olap.Query monQuery;
494     try {
495       monQuery = parseMDX();
496     } catch (OlapException e) {
497       String JavaDoc err = e.getMessage();
498       logger.error(err);
499       throw new OlapException(err);
500     }
501     resetMetaData(monQuery); // reset the model data
502

503     queryAdapter = new MondrianQueryAdapter(this, monQuery);
504
505     MondrianSortRank sortExt = (MondrianSortRank) getExtension(SortRank.ID);
506     if (sortExt != null)
507       sortExt.reset();
508
509     isInitialized = true;
510
511     // as initialization is complete, notify extensions
512
Map JavaDoc extMap = getExtensions();
513     Collection JavaDoc extensions = extMap.values();
514     for (Iterator JavaDoc iter = extensions.iterator(); iter.hasNext();) {
515       Extension extension = (Extension) iter.next();
516       extension.modelInitialized();
517     }
518   }
519
520   /**
521    * parse
522    *
523    * @return @throws OlapException
524    */

525   private mondrian.olap.Query parseMDX() throws OlapException {
526     mondrian.olap.Query monQuery;
527     try {
528       monQuery = getConnection().parseQuery(mdxQuery);
529     } catch (MondrianException ex) {
530       logger.error("Parse Failure", ex);
531       // try to get a meaningfullerror message on parse failure
532
Throwable JavaDoc rootCause = getRootCause(ex);
533       throw new OlapException(rootCause.getMessage());
534     } catch (Exception JavaDoc ex) {
535       // not expected
536
logger.fatal("unexpected parse failure " + ex.getMessage());
537       throw new OlapException(ex);
538     }
539     if (monQuery == null) {
540       logger.fatal("unexpected parse failure");
541       throw new OlapException("unexpected parse failure");
542     }
543     return monQuery;
544   }
545
546   /**
547    * find root cause for exception
548    */

549   private Throwable JavaDoc getRootCause(MondrianException ex) {
550     Throwable JavaDoc rootCause = ex;
551     Throwable JavaDoc cause = ex.getCause();
552     while (cause != null && cause != rootCause) {
553       rootCause = cause;
554       cause = cause.getCause();
555     }
556     return rootCause;
557   }
558
559   /**
560    * add Dimension to Hashtable, if not already there
561    *
562    * @param monDimension -
563    * the "key" is the Mondrian Dimension
564    */

565   private void addDimension(mondrian.olap.Dimension monDimension) {
566     String JavaDoc uniqueName = monDimension.getUniqueName();
567     if (!hDimensions.containsKey(uniqueName)) {
568       MondrianDimension dimension = new MondrianDimension(monDimension, this);
569       hDimensions.put(uniqueName, dimension);
570       // make sure, that all hierarchies are initialized
571
mondrian.olap.Hierarchy[] monHiers = monDimension.getHierarchies();
572       for (int i = 0; i < monHiers.length; i++) {
573         this.addHierarchy(monHiers[i], dimension);
574       }
575     }
576   }
577
578   /**
579    * add Hierarchy to Hashtable, if not already there
580    *
581    * @param monHierarchy -
582    * the "key" is the Mondrian Hierarchy
583    */

584   private void addHierarchy(mondrian.olap.Hierarchy monHierarchy, MondrianDimension dimension) {
585     String JavaDoc uniqueName = monHierarchy.getUniqueName();
586     if (!hHierarchies.containsKey(uniqueName)) {
587       MondrianHierarchy hierarchy = new MondrianHierarchy(monHierarchy, dimension, this);
588       hHierarchies.put(uniqueName, hierarchy);
589       // make sure, that all levels are initialized
590
SchemaReader scr = monConnection.getSchemaReader();
591       mondrian.olap.Level[] monLevels = scr.getHierarchyLevels(monHierarchy);
592       for (int i = 0; i < monLevels.length; i++) {
593         this.addLevel(monLevels[i], hierarchy);
594       }
595     }
596   }
597
598   /**
599    * add Level to Hashtable, if not already there
600    *
601    * @param monLevel -
602    * the "key" is the Mondrian Level
603    */

604   protected void addLevel(mondrian.olap.Level monLevel, MondrianHierarchy hierarchy) {
605     String JavaDoc uniqueName = monLevel.getUniqueName();
606     if (!hLevels.containsKey(uniqueName)) {
607       MondrianLevel level = new MondrianLevel(monLevel, hierarchy, this);
608       hLevels.put(uniqueName, level);
609     }
610   }
611
612   /**
613    * add Member to Hashtable, if not already there
614    *
615    * @param monMember -
616    * the "key" is the Mondrian Member
617    * @return the corresponding member
618    */

619   public MondrianMember addMember(mondrian.olap.Member monMember) {
620     String JavaDoc uniqueName = monMember.getUniqueName();
621     if (hMembers.containsKey(uniqueName)) {
622       return (MondrianMember) hMembers.get(uniqueName);
623     } else {
624       mondrian.olap.Level monLevel = monMember.getLevel();
625       MondrianLevel level = this.lookupLevel(monLevel.getUniqueName());
626       MondrianMember member = new MondrianMember(monMember, level, this);
627       hMembers.put(uniqueName, member);
628       if (monMember.isMeasure())
629         aMeasures.add(member);
630       return member;
631     }
632   }
633
634   /**
635    * remove Member from Hashtable (for a calculated member)
636    *
637    * @param uniqueName
638    */

639   public void removeMember(String JavaDoc uniqueName) {
640     if (hMembers.containsKey(uniqueName)) {
641       MondrianMember m = (MondrianMember) hMembers.get(uniqueName);
642       if (aMeasures.contains(m))
643         aMeasures.remove(m);
644       hMembers.remove(uniqueName);
645     }
646   }
647
648   /**
649    * find the Dimension.
650    *
651    * @param uniqueName
652    * is the search key (
653    * @return the corresponding MondrianDimension
654    */

655   public MondrianDimension lookupDimension(String JavaDoc uniqueName) {
656     return (MondrianDimension) hDimensions.get(uniqueName);
657   }
658
659   /**
660    * find the Hierarchy in the dimensions.
661    *
662    * @param uniqueName
663    * is the search key
664    * @return the corresponding hierarchy
665    */

666   public MondrianHierarchy lookupHierarchy(String JavaDoc uniqueName) {
667     return (MondrianHierarchy) hHierarchies.get(uniqueName);
668   }
669
670   /**
671    * find member in the Olap Hierarchy.
672    *
673    * @param uniqueName
674    * is the search key (Mondrian member unique name)
675    * @return the corresponding member
676    */

677   public Member lookupMemberByUName(String JavaDoc uniqueName) {
678     // if the unique name was stored in a memento,
679
// it is possible, that
680
// - the member was not loaded yet
681
// - the member was removed from the schema meanwhile
682
MondrianMember m = (MondrianMember) hMembers.get(uniqueName);
683     if (m != null)
684       return m;
685     final SchemaReader scr = this.getConnection().getSchemaReader();
686
687     String JavaDoc[] uniqueNameParts = Util.explode(uniqueName);
688
689     /*
690      * Pattern pat = Pattern.compile("\\[([^\\]]+)\\]"); Matcher mat =
691      * pat.matcher(uniqueName); int i = 0; ArrayList aName = new ArrayList();
692      * while (mat.find()) { String group = mat.group(1); aName.add(group); }
693      * String[] uniqueNameParts = (String[])aName.toArray(new String[0]);
694      */

695
696     Cube cube = queryAdapter.getMonQuery().getCube();
697     mondrian.olap.Member monMember = (mondrian.olap.Member) Util.lookupCompound(scr, cube,
698         uniqueNameParts, false, Category.Member);
699     if (monMember != null)
700       return addMember(monMember);
701
702     if (monMember == null) {
703       // there's still a chance to find the member
704
// as a calculated member in a formula
705
Formula[] formulas = queryAdapter.getMonQuery().getFormulas();
706       for (int i = 0; i < formulas.length; i++) {
707         monMember = formulas[i].getMdxMember();
708         if (uniqueName.equals(monMember.getUniqueName()))
709           return addMember(monMember);
710       }
711     }
712     return null;
713   }
714
715   /**
716    * find level in the Olap Hierarchy.
717    *
718    * @param uniqueName
719    * is the search key (Mondrian level)
720    * @return the corresponding level
721    */

722   public MondrianLevel lookupLevel(String JavaDoc uniqueName) {
723     return (MondrianLevel) hLevels.get(uniqueName);
724   }
725
726   /**
727    * @return true if dimension and all of its hierachies can be
728    * accessed according to role
729    */

730   private boolean canAccess(mondrian.olap.Dimension dim) {
731     Role role = this.monConnection.getRole();
732     if (!role.canAccess(dim))
733       return false;
734     mondrian.olap.Hierarchy[] hiers = dim.getHierarchies();
735     for (int i = 0; i < hiers.length; i++) {
736       if (role.canAccess(hiers[i]))
737         return true;
738     }
739     return false;
740   }
741
742   /**
743    * reset the model Hashtables.
744    */

745   private void resetMetaData(mondrian.olap.Query monQuery) {
746     this.hDimensions = new HashMap JavaDoc();
747     this.hHierarchies = new HashMap JavaDoc();
748     this.hLevels = new HashMap JavaDoc();
749     this.hMembers = new HashMap JavaDoc();
750     this.aMeasures = new ArrayList JavaDoc();
751
752     // initialize meta data
753
mondrian.olap.Cube cube = monQuery.getCube();
754     mondrian.olap.Dimension[] monDims = cube.getDimensions();
755     for (int i = 0; i < monDims.length; i++) {
756       // Is the dimension accessable?
757
if (canAccess(monDims[i]))
758         this.addDimension(monDims[i]);
759     }
760
761     SchemaReader sr = cube.getSchemaReader(null);
762     for (int i = 0; i < monDims.length; i++) {
763       mondrian.olap.Hierarchy[] monHiers = monDims[i].getHierarchies();
764       for (int j = 0; j < monHiers.length; j++) {
765         List JavaDoc calcMembers = sr.getCalculatedMembers(monHiers[j]);
766         for (Iterator JavaDoc it = calcMembers.iterator(); it.hasNext();) {
767           this.addMember((mondrian.olap.Member) it.next());
768         }
769       }
770     }
771   }
772
773   /**
774    * get the Mondrian Connection
775    *
776    * @return The Mondrian Connection
777    */

778   public mondrian.olap.Connection getConnection() {
779     return monConnection;
780   }
781
782   /**
783    * get the MDX for the user to edit
784    *
785    * @return current MDX statement
786    * @see MdxOlapModel#getCurrentMdx()
787    */

788   public String JavaDoc getCurrentMdx() {
789     // if the model was changed, due to ModelChangeListener,
790
// then the current MDX is not really "current"
791
if (result != null)
792       return currentMdx;
793     else if (queryAdapter == null) {
794       return mdxQuery;
795     } else {
796       // get new result, this will update the mdx
797
try {
798         getResult();
799       } catch (Exception JavaDoc e) {
800         logger.error("unexpected Exeption getResult " + e.toString());
801         throw new RuntimeException JavaDoc(e);
802       }
803       return currentMdx;
804     }
805   }
806
807   /**
808    * set the mdx entered by the user.
809    *
810    * @task error handling: restore mdx in case of error
811    * @throws OlapException
812    * if the syntax is invalid
813    * @param mdxQuery
814    */

815   boolean setUserMdx(String JavaDoc mdxQuery) throws OlapException {
816     if (this.currentMdx.equals(mdxQuery))
817       return false;
818
819     String JavaDoc saveMdx = this.mdxQuery;
820     this.mdxQuery = mdxQuery;
821     if (logger.isInfoEnabled())
822       logger.info("setUserMdx =" + mdxQuery);
823
824     mondrian.olap.Query monQuery = null;
825     try {
826       monQuery = parseMDX();
827     } catch (OlapException e) {
828       logger.error("setUserMdx failed " + e.getMessage());
829       // parse failed, restore old mdx
830
this.mdxQuery = saveMdx;
831       throw e; // re-throw
832
}
833     resetMetaData(monQuery); // reset the model data
834

835     queryAdapter = new MondrianQueryAdapter(this, monQuery);
836
837     // no exception gotten
838
MondrianSortRank sortExt = (MondrianSortRank) getExtension(SortRank.ID);
839     if (sortExt != null)
840       sortExt.reset();
841
842     result = null;
843     this.currentMdx = mdxQuery.replace('\r', ' ');
844
845     return true;
846   }
847
848   /**
849    * Returns the mdxQuery.
850    *
851    * @return String
852    */

853   protected String JavaDoc getMdxQuery() {
854     return mdxQuery;
855   }
856
857   public Object JavaDoc getRootDecoree() {
858     return this;
859   }
860
861   /**
862    * session terminated, closing connections etc
863    */

864   public void destroy() {
865     logger.info(null);
866     super.destroy();
867     if (monConnection != null) {
868       if (logger.isDebugEnabled())
869         logger.debug("MondrianModel: closing connection " + monConnection);
870       monConnection.close();
871       monConnection = null;
872     }
873     this.sessionId = null;
874   }
875
876   /**
877    * Sets the currentMdx.
878    *
879    * @param currentMdx
880    * The currentMdx to set
881    */

882   protected void setCurrentMdx(String JavaDoc currentMdx) {
883     //this.currentMdx = currentMdx.replace('\r', ' ');
884
this.currentMdx = currentMdx.replaceAll("\r", "");
885   }
886
887   /**
888    * Returns the monConnection.
889    *
890    * @return mondrian.olap.Connection
891    */

892   protected mondrian.olap.Connection getMonConnection() {
893     return monConnection;
894   }
895
896   /**
897    * Get jdbcDriver.
898    *
899    * @return jdbcDriver
900    */

901   protected String JavaDoc getJdbcDriver() {
902     return jdbcDriver;
903   }
904
905   /**
906    * Get connectString.
907    *
908    * @return connectString.
909    */

910   protected String JavaDoc getConnectString() {
911     return connectString;
912   }
913
914   /**
915    * Get connectProperties
916    *
917    * @return connectProperties.
918    */

919   protected Util.PropertyList getConnectProperties() {
920     return connectProperties;
921   }
922
923   /**
924    * create a Memento bean object holding current state.
925    *
926    * @return MondrianMemento current state
927    */

928   public Object JavaDoc getBookmarkState(int levelOfDetail) {
929     if (this.result == null)
930       return null;
931     try {
932       if (levelOfDetail == Bookmarkable.EXTENSIONAL)
933         return getExtensionalBookmarkState();
934       return getIntensionalBookmarkState();
935     } catch (OlapException e) {
936       logger.error(null, e);
937       throw new RuntimeException JavaDoc(e);
938     }
939   }
940
941   /**
942    * creates a bookmark that will contail as much detail as possible. But this
943    * bookmark may not work when the data in the cube have changed.
944    */

945   private Object JavaDoc getExtensionalBookmarkState() throws OlapException {
946     MondrianMemento memento = createMemento();
947     // set the MDX query string
948
// When the state is reset, this mdx will be parsed as the
949
// startup query.
950
memento.setMdxQuery(currentMdx);
951     boolean useQuax = queryAdapter.isUseQuax();
952     memento.setUseQuax(useQuax);
953     if (useQuax) {
954       MondrianQuax[] quaxes = (MondrianQuax[]) queryAdapter.getQuaxes();
955       MondrianQuaxBean[] quaxBeans = new MondrianQuaxBean[quaxes.length];
956       for (int i = 0; i < quaxes.length; i++) {
957         quaxBeans[i] = new MondrianQuaxBean();
958         beanFromQuax(quaxBeans[i], quaxes[i]);
959       } // for i quaxes
960
// set quaxes to memento
961
memento.setQuaxes(quaxBeans);
962     }
963     return memento;
964   }
965
966   /**
967    * creates a bookmark that will contail only those data, that are independent
968    * of the data in the cube. This bookmark can be restored even after
969    * cube data has changed.
970    */

971   private Object JavaDoc getIntensionalBookmarkState() throws OlapException {
972     MondrianMemento memento = createMemento();
973     memento.setUseQuax(true);
974     MondrianAxis[] axes = (MondrianAxis[]) result.getAxes();
975     MondrianQuaxBean[] quaxBeans = new MondrianQuaxBean[axes.length];
976     for (int i = 0; i < axes.length; i++)
977       quaxBeans[i] = intensionalQuaxBeanFromAxis(axes[i]);
978     memento.setQuaxes(quaxBeans);
979     // create the appropriate mdx query
980
// calculated measures will be adopted if they
981
// only deal with measures.
982
String JavaDoc newMdx = intensionalMdx(quaxBeans);
983     memento.setMdxQuery(newMdx);
984     return memento;
985   }
986
987   /**
988    * create intensional MDX query
989    */

990   private String JavaDoc intensionalMdx(MondrianQuaxBean[] quaxBeans) {
991
992     String JavaDoc saveMdx = this.currentMdx;
993     Query cloneQuery = queryAdapter.getMonQuery().safeClone();
994     MondrianQuax quaxes[] = new MondrianQuax[quaxBeans.length];
995     for (int i = 0; i < quaxes.length; i++) {
996       MondrianQuax q = (MondrianQuax) queryAdapter.getQuaxes()[i];
997       QueryAxis a = queryAdapter.getMonQuery().axes[i];
998       quaxes[i] = new MondrianQuax(q.getOrdinal(), a, this);
999     }
1000    try {
1001      quaxesFromBeans(quaxes, quaxBeans);
1002    } catch (OlapException e) {
1003      logger.error(null, e);
1004      throw new IllegalArgumentException JavaDoc(e.toString());
1005    }
1006    MondrianQuax saveQuaxes[] = (MondrianQuax[]) queryAdapter.getQuaxes();
1007    queryAdapter.setQuaxes(quaxes);
1008    Query mQuery = queryAdapter.getMonQuery();
1009    // clear slicer, members in slicer are supposed to be not intensional
1010
mQuery.setSlicerAxis(null);
1011    // regenerate query from quaxes
1012
queryAdapter.onExecute();
1013    // remove formulas, which are not for measures only
1014
Formula[] formulas = mQuery.getFormulas();
1015    for (int i = 0; i < formulas.length; i++) {
1016      mondrian.olap.Member m = formulas[i].getMdxMember();
1017      boolean remove = false;
1018      if (!m.getDimension().isMeasures()) {
1019        // not measures - remove
1020
remove = true;
1021      } else {
1022        mondrian.olap.Exp exp = formulas[i].getExpression();
1023        if (notMeasures(exp))
1024          remove = true;
1025      }
1026      if (remove) {
1027        String JavaDoc name = formulas[i].getMdxMember().getUniqueName();
1028        if (mQuery.canRemoveFormula(name)) {
1029          mQuery.removeFormula(name, true);
1030        } else {
1031          logger.fatal("cannot remove formula " + formulas[i].getName());
1032        }
1033      }
1034    }
1035
1036    queryAdapter.onExecute(); // rewrites currentMdx
1037
String JavaDoc newMdx = this.currentMdx;
1038    // restore state
1039
queryAdapter.setQuaxes(saveQuaxes);
1040    queryAdapter.setMonQuery(cloneQuery);
1041    this.currentMdx = saveMdx;
1042    return newMdx;
1043  }
1044
1045  /**
1046   * @return true,
1047   * if exp refers to members of a dimension other than Measures
1048   */

1049  private boolean notMeasures(mondrian.olap.Exp exp) {
1050    if (exp instanceof mondrian.olap.Member) {
1051      mondrian.olap.Member m = (mondrian.olap.Member) exp;
1052      if (m.getDimension().isMeasures())
1053        return false;
1054      else
1055        return true;
1056    } else if (exp instanceof mondrian.olap.FunCall) {
1057      mondrian.olap.FunCall f = (FunCall) exp;
1058      Exp[] args = f.getArgs();
1059      for (int i = 0; i < args.length; i++) {
1060        if (notMeasures(args[i]))
1061          return true;
1062      }
1063    }
1064    return false;
1065  }
1066
1067  /**
1068   * creates a memento which is initialized except the quaxes and mdxQuery
1069   * @see Memento#setQuaxes
1070   */

1071  private MondrianMemento createMemento() {
1072    MondrianMemento memento = new MondrianMemento();
1073    memento.setJdbcDriver(jdbcDriver);
1074    memento.setConnectString(connectString);
1075    memento.setVersion(MondrianMemento.CURRENT_VERSION);
1076    // axes swapped
1077
memento.setAxesSwapped(queryAdapter.isSwapAxes());
1078    // sorting
1079
MondrianSortRank sortExt = (MondrianSortRank) getExtension(SortRank.ID);
1080    if (sortExt != null)
1081      storeSort(sortExt, memento);
1082    return memento;
1083  }
1084
1085  private MondrianQuaxBean intensionalQuaxBeanFromAxis(MondrianAxis axis) throws OlapException {
1086    MondrianQuaxBean quaxBean = new MondrianQuaxBean();
1087    MondrianHierarchy[] hiers = (MondrianHierarchy[]) axis.getHierarchies();
1088
1089    quaxBean.setNDimension(hiers.length);
1090    quaxBean.setHierarchizeNeeded(false);
1091    quaxBean.setOrdinal(axis.getOrdinal());
1092
1093    PositionNodeBean rootBean = new PositionNodeBean();
1094    rootBean.setReference(null);
1095    quaxBean.setPosTreeRoot(rootBean);
1096
1097    PositionNodeBean parentBean = rootBean;
1098    for (int i = 0; i < hiers.length; i++) {
1099      ExpBean expBean;
1100      if (hiers[i].getDimension().isMeasure()) {
1101        Exp exp = createMeasuresExp(axis, i);
1102        expBean = createBeanFromExp(exp);
1103      } else {
1104        // the toplevel members may have changed when the bookmark is restored,
1105
// so we can not compute them here
1106
ExpBean hierBean = createBeanFromExp(hiers[i].getMonHierarchy());
1107        expBean = new ExpBean();
1108        expBean.setType(ExpBean.TYPE_TOPLEVEL_MEMBERS);
1109        expBean.setArgs(new ExpBean[] { hierBean});
1110      }
1111      PositionNodeBean nodeBean = new PositionNodeBean();
1112      nodeBean.setReference(expBean);
1113      parentBean.setChildren(new PositionNodeBean[] { nodeBean});
1114      parentBean = nodeBean;
1115    }
1116    return quaxBean;
1117  }
1118
1119  /**
1120   * creates an Exp for the visible Measures
1121   * @param axis the axis containing the measures dimension
1122   * @param i the i-th hiararchy on that axis is the measures hierarchy
1123   */

1124  private Exp createMeasuresExp(MondrianAxis axis, int i) {
1125    List JavaDoc measuresList = new ArrayList JavaDoc();
1126    Set JavaDoc measuresSet = new HashSet JavaDoc();
1127    for (Iterator JavaDoc it = axis.getPositions().iterator(); it.hasNext();) {
1128      MondrianPosition mp = (MondrianPosition) it.next();
1129      MondrianMember member = (MondrianMember) mp.getMembers()[i];
1130      if (measuresSet.add(member))
1131        measuresList.add(new MemberExpr(member.getMonMember()));
1132    }
1133    if (measuresList.size() == 1)
1134      return (Exp) measuresList.get(0);
1135    Exp[] args = (Exp[]) measuresList.toArray(new Exp[measuresList.size()]);
1136    return new UnresolvedFunCall("{}", Syntax.Braces, args);
1137  }
1138
1139  /**
1140   * restore state from Memento.
1141   *
1142   * @param state bean to be restored
1143   */

1144  public void setBookmarkState(Object JavaDoc state) {
1145
1146    MondrianMemento memento = (MondrianMemento) state;
1147    int version = memento.getVersion();
1148    if (version <= 1) {
1149      logger.warn("Bookmark is of old state (not supported any more in the future)!\nPlease save again!");
1150    }
1151    mdxQuery = memento.getMdxQuery();
1152
1153    if (isInitialized) {
1154      // already initialized, only new query adapter needed
1155

1156      mondrian.olap.Query monQuery = null;
1157      try {
1158        monQuery = parseMDX();
1159      } catch (OlapException e) {
1160        // should really not occur
1161
String JavaDoc err = e.getMessage();
1162        logger.fatal(err);
1163        throw new RuntimeException JavaDoc(err);
1164      }
1165
1166      // bookmark may change the cube of the same schema (aml does so)
1167
resetMetaData(monQuery);
1168
1169      queryAdapter = new MondrianQueryAdapter(this, monQuery);
1170
1171      MondrianSortRank sortExt = (MondrianSortRank) getExtension(SortRank.ID);
1172      if (sortExt != null)
1173        sortExt.reset();
1174
1175    } else {
1176
1177      connectString = memento.getConnectString();
1178      jdbcDriver = memento.getJdbcDriver();
1179
1180      // Regardless of any state, we will have to process the start MDX.
1181
// It might contain WITH MEMBER declarations, which must not be lost.
1182
// The start MDX is processed in the QueryAdapter c'tor.
1183
try {
1184        initialize();
1185      } catch (OlapException e) {
1186        // should really not occur
1187
String JavaDoc err = e.getMessage();
1188        logger.fatal(err);
1189        throw new RuntimeException JavaDoc(err);
1190      }
1191      // do we have to get a result ???
1192
}
1193
1194    boolean useQuax = true;
1195    if (version >= 3) {
1196      useQuax = memento.isUseQuax();
1197      queryAdapter.setUseQuax(useQuax);
1198    }
1199
1200    if (useQuax) {
1201      // reset the Quaxes to current state
1202
MondrianQuaxBean[] quaxBeans = (MondrianQuaxBean[]) memento.getQuaxes();
1203      MondrianQuax quaxes[] = (MondrianQuax[]) queryAdapter.getQuaxes();
1204      // update the quaxes
1205

1206      if (version <= 1) {
1207        for (int i = 0; i < quaxes.length; i++) {
1208          boolean qubonMode = quaxBeans[i].isQubonMode();
1209
1210          quaxes[i].setQubonMode(qubonMode);
1211
1212          // handle old stuff as of MDX version 2
1213
if (qubonMode)
1214            MondrianOldStuff.handleQubonMode(quaxes[i], quaxBeans[i], this);
1215          else
1216            MondrianOldStuff.handleDrillExMode(quaxes[i], quaxBeans[i], this);
1217        }
1218      } else {
1219        try {
1220          quaxesFromBeans(quaxes, quaxBeans);
1221        } catch (OlapException e) {
1222          logger.error(null, e);
1223          throw new IllegalArgumentException JavaDoc(e.toString());
1224        }
1225      }
1226    }
1227
1228    // sorting
1229
MondrianSortRank sortExt = (MondrianSortRank) getExtension(SortRank.ID);
1230    restoreSort(sortExt, memento);
1231
1232    // swap axes if neccessary
1233
queryAdapter.setSwapAxes(memento.isAxesSwapped());
1234
1235    // we can not fire a structureChanged event here because the bookmark state
1236
// of other (GUI) components may already be restored. If they receive a
1237
// structureChanged they would throw away their bookmark state.
1238
fireModelChanged();
1239  }
1240
1241  private Exp[] createExpsFromBeans(ExpBean[] beans) throws OlapException {
1242    Exp[] exps = new Exp[beans.length];
1243    for (int i = 0; i < beans.length; i++) {
1244      exps[i] = (Exp) createExpFromBean(beans[i]);
1245    }
1246    return exps;
1247  }
1248
1249  /**
1250   * create Mondrian exp from expBean
1251   * @param expBean
1252   * @return @throws
1253   * OlapException
1254   */

1255  protected Object JavaDoc createExpFromBean(ExpBean expBean) throws OlapException {
1256    if (expBean.getType() == ExpBean.TYPE_TOPLEVEL_MEMBERS) {
1257      SchemaReader scr = getMonConnection().getSchemaReader();
1258      Exp[] args = createExpsFromBeans(expBean.getArgs());
1259      HierarchyExpr he = (HierarchyExpr) args[0];
1260      mondrian.olap.Hierarchy h = he.getHierarchy();
1261      return MondrianUtil.topLevelMembers(h, false, scr);
1262    }
1263
1264    if (expBean.getType() == ExpBean.TYPE_MEMBER) {
1265      MondrianMember member = (MondrianMember) lookupMemberByUName(expBean.getName());
1266      if (member == null) {
1267        // probably schema changed, cannot restore state
1268
throw new OlapException("could not find member " + expBean.getName());
1269      }
1270      return new MemberExpr(member.getMonMember());
1271    }
1272
1273    if (expBean.getType() == ExpBean.TYPE_FUNCALL) {
1274      // FunCall
1275
String JavaDoc name = expBean.getName();
1276      ExpBean[] argBeans = expBean.getArgs();
1277      Exp[] args = createExpsFromBeans(argBeans);
1278      if ("Parameter".equalsIgnoreCase(name)) {
1279        String JavaDoc paramId = String.valueOf(((Literal)args[0]).getValue());
1280        Exp value = args[2];
1281        Type type = value.getType();
1282        String JavaDoc descr = "";
1283        if (args.length == 4)
1284          descr = (String JavaDoc)((Literal)args[3]).getValue();
1285        return new ParameterExpr(new ParameterImpl(paramId, value, descr, type));
1286      } else if ("ParamRef".equalsIgnoreCase(name)) {
1287        // FIXME support ParamRef
1288
throw new IllegalArgumentException JavaDoc(name);
1289      }
1290      Syntax syntax = MondrianUtil.funCallSyntax(name, argBeans.length);
1291      return new UnresolvedFunCall(name, syntax, args);
1292    }
1293
1294    if (expBean.getType() == ExpBean.TYPE_LEVEL) {
1295      // Level
1296
MondrianLevel lev = this.lookupLevel(expBean.getName());
1297      if (lev == null) {
1298        // probably schema changed, cannot restore state
1299
throw new OlapException("could not find Level " + expBean.getName());
1300      }
1301      return new LevelExpr(lev.getMonLevel());
1302    } else if (expBean.getType() == ExpBean.TYPE_HIER) {
1303      // Hierarchy
1304
MondrianHierarchy hier = this.lookupHierarchy(expBean.getName());
1305      if (hier == null) {
1306        // probably schema changed, cannot restore state
1307
throw new OlapException("could not find Hierarchy " + expBean.getName());
1308      }
1309      return new HierarchyExpr(hier.getMonHierarchy());
1310    } else if (expBean.getType() == ExpBean.TYPE_DIM) {
1311      // Dimension
1312
MondrianDimension dim = this.lookupDimension(expBean.getName());
1313      if (dim == null) {
1314        // probably schema changed, cannot restore state
1315
throw new OlapException("could not find Dimension " + expBean.getName());
1316      }
1317      return new DimensionExpr(dim.getMonDimension());
1318    } else if (expBean.getType() == ExpBean.TYPE_STRING_LITERAL) {
1319      // String literal
1320
String JavaDoc str = (String JavaDoc) expBean.getLiteralValue();
1321      return Literal.createString(str);
1322    } else if (expBean.getType() == ExpBean.TYPE_INTEGER_LITERAL) {
1323      // Integer literal
1324
Integer JavaDoc iii = (Integer JavaDoc) expBean.getLiteralValue();
1325      return Literal.create(iii);
1326    } else if (expBean.getType() == ExpBean.TYPE_DOUBLE_LITERAL) {
1327      // Double literal
1328
Double JavaDoc ddd = (Double JavaDoc) expBean.getLiteralValue();
1329      return Literal.create(ddd);
1330    } else
1331      throw new OlapException("Invalid ExpBean Type " + expBean.getType());
1332
1333  }
1334
1335  protected ExpBean createBeanFromExp(Object JavaDoc exp) throws OlapException {
1336    ExpBean bean = new ExpBean();
1337
1338    if (exp instanceof mondrian.olap.Member) {
1339      mondrian.olap.Member m = (mondrian.olap.Member) exp;
1340      bean.setType(ExpBean.TYPE_MEMBER);
1341      bean.setName(m.getUniqueName());
1342      bean.setArgs(new ExpBean[0]);
1343    } else if (exp instanceof mondrian.olap.Level) {
1344      mondrian.olap.Level lev = (mondrian.olap.Level) exp;
1345      bean.setType(ExpBean.TYPE_LEVEL);
1346      bean.setName(lev.getUniqueName());
1347      bean.setArgs(new ExpBean[0]);
1348    } else if (exp instanceof mondrian.olap.Hierarchy) {
1349      mondrian.olap.Hierarchy hier = (mondrian.olap.Hierarchy) exp;
1350      bean.setType(ExpBean.TYPE_HIER);
1351      bean.setName(hier.getUniqueName());
1352      bean.setArgs(new ExpBean[0]);
1353    } else if (exp instanceof mondrian.olap.Dimension) {
1354      mondrian.olap.Dimension dim = (mondrian.olap.Dimension) exp;
1355      bean.setType(ExpBean.TYPE_DIM);
1356      bean.setName(dim.getUniqueName());
1357      bean.setArgs(new ExpBean[0]);
1358    } else if (exp instanceof mondrian.olap.FunCall) {
1359      FunCall f = (FunCall) exp;
1360      bean.setType(ExpBean.TYPE_FUNCALL);
1361      bean.setName(f.getFunName());
1362      bean.setArgs(createBeansFromExps(f.getArgs()));
1363    } else if (exp instanceof ParameterExpr) {
1364      Parameter p = ((ParameterExpr)exp).getParameter();
1365      bean.setType(ExpBean.TYPE_FUNCALL);
1366      bean.setName("Parameter");
1367      bean.setArgs(createBeansFromExps(getParamterArgs(p)));
1368      // FIXME support ParamRef
1369
} else if (exp instanceof MemberExpr) {
1370      mondrian.olap.Member m = ((MemberExpr) exp).getMember();
1371      bean.setType(ExpBean.TYPE_MEMBER);
1372      bean.setName(m.getUniqueName());
1373      bean.setArgs(new ExpBean[0]);
1374    } else if (exp instanceof LevelExpr) {
1375      mondrian.olap.Level lev = ((LevelExpr) exp).getLevel();
1376      bean.setType(ExpBean.TYPE_LEVEL);
1377      bean.setName(lev.getUniqueName());
1378      bean.setArgs(new ExpBean[0]);
1379    } else if (exp instanceof HierarchyExpr) {
1380      mondrian.olap.Hierarchy hier = ((HierarchyExpr) exp).getHierarchy();
1381      bean.setType(ExpBean.TYPE_HIER);
1382      bean.setName(hier.getUniqueName());
1383      bean.setArgs(new ExpBean[0]);
1384    } else if (exp instanceof DimensionExpr) {
1385      mondrian.olap.Dimension dim = ((DimensionExpr) exp).getDimension();
1386      bean.setType(ExpBean.TYPE_DIM);
1387      bean.setName(dim.getUniqueName());
1388      bean.setArgs(new ExpBean[0]);
1389    } else if (exp instanceof mondrian.olap.Literal) {
1390      mondrian.olap.Literal lit = (mondrian.olap.Literal) exp;
1391      Object JavaDoc val = lit.getValue();
1392      if (lit.getCategory() == Category.Numeric) {
1393        if (val instanceof Integer JavaDoc)
1394          bean.setType(ExpBean.TYPE_INTEGER_LITERAL);
1395        else
1396          bean.setType(ExpBean.TYPE_DOUBLE_LITERAL);
1397      } else {
1398        // String || Symbol
1399
bean.setType(ExpBean.TYPE_STRING_LITERAL);
1400      }
1401      bean.setLiteralValue(val);
1402      bean.setArgs(new ExpBean[0]);
1403    } else {
1404      logger.fatal("cannot create ExpBean type =" + exp.getClass().toString());
1405      throw new IllegalArgumentException JavaDoc(exp.getClass().toString());
1406    }
1407
1408    return bean;
1409  }
1410
1411  /**
1412   * The "compiled expressions" crap mess up everything.
1413   * <p />
1414   * Returns the arguments are needed to create an UnresolvedFunCall
1415   * from a Parameter.
1416   */

1417  private Exp[] getParamterArgs(Parameter p) {
1418    Exp expName = Literal.createString(p.getName());
1419    Exp expValue;
1420    Exp expType;
1421
1422    // unwrap the various wrappers of the parameter value
1423
Object JavaDoc objValue = p.getValue();
1424    if (objValue == null)
1425      objValue = p.getDefaultExp();
1426    if (objValue instanceof Literal)
1427      objValue = ((Literal)objValue).getValue();
1428    else if (objValue instanceof MemberExpr)
1429      objValue = ((MemberExpr)objValue).getMember();
1430
1431    if (objValue instanceof String JavaDoc) {
1432      expValue = Literal.createString((String JavaDoc)objValue);
1433      expType = Literal.createSymbol("STRING");
1434    } else if (objValue instanceof Double JavaDoc) {
1435      expValue = Literal.create((Double JavaDoc)objValue);
1436      expType = Literal.createSymbol("NUMBER");
1437    } else if (objValue instanceof Integer JavaDoc) {
1438      expValue = Literal.create((Integer JavaDoc)objValue);
1439      expType = Literal.createSymbol("NUMBER");
1440    } else if (objValue instanceof mondrian.olap.Member) {
1441      mondrian.olap.Member m = (mondrian.olap.Member)objValue;
1442      expValue = new MemberExpr(m);
1443      expType = new DimensionExpr(m.getDimension());
1444    } else throw new IllegalArgumentException JavaDoc("unknown Param value: " + objValue + ": " + objValue.getClass());
1445
1446    Exp expDescr = p.getDescription() != null ? Literal.createString(p.getDescription()):Literal.createString("");
1447    return new Exp[]{expName, expType, expValue, expDescr};
1448  }
1449
1450  private ExpBean[] createBeansFromExps(Object JavaDoc[] exps) throws OlapException {
1451    ExpBean[] beans = new ExpBean[exps.length];
1452    for (int i = 0; i < exps.length; i++) {
1453      beans[i] = createBeanFromExp(exps[i]);
1454    }
1455    return beans;
1456  }
1457
1458  public DataSource JavaDoc getSqlDataSource() {
1459    return ((RolapConnection) monConnection).getDataSource();
1460  }
1461
1462  public String JavaDoc getDynresolver() {
1463    return dynresolver;
1464  }
1465
1466  public void setDynresolver(String JavaDoc dynresolver) {
1467    this.dynresolver = dynresolver;
1468  }
1469
1470  public void setServletContext(ServletContext JavaDoc servletContext) {
1471    this.servletContext = servletContext;
1472  }
1473
1474  /**
1475   * get Mondrian Connection Pooling property
1476   */

1477  public boolean isConnectionPooling() {
1478    return connectionPooling;
1479  }
1480
1481  /**
1482   * set Mondrian Connection Pooling property
1483   */

1484  public void setConnectionPooling(boolean connectionPooling) {
1485    this.connectionPooling = connectionPooling;
1486  }
1487
1488  /**
1489   * get the external DataSource to be used by Mondrian
1490   */

1491  public DataSource JavaDoc getExternalDataSource() {
1492    return externalDataSource;
1493  }
1494
1495  /**
1496   * set the external DataSource to be used by Mondrian
1497   */

1498  public void setExternalDataSource(DataSource JavaDoc externalDataSource) {
1499    this.externalDataSource = externalDataSource;
1500  }
1501  /**
1502   * Getter for property dynLocale.
1503   * @return Value of property dynLocale.
1504   */

1505  public String JavaDoc getDynLocale() {
1506      return this.dynLocale;
1507  }
1508
1509  /**
1510   * Setter for property dynLocale.
1511   * @param dynLocale New value of property dynLocale.
1512   */

1513  public void setDynLocale(String JavaDoc dynLocale) {
1514      this.dynLocale = dynLocale;
1515  }
1516
1517  /**
1518   * Getter for property dataSourceChangeListener.
1519   * @return Value of property dataSourceChangeListener.
1520   */

1521  public String JavaDoc getDataSourceChangeListener() {
1522      return this.dataSourceChangeListener;
1523  }
1524
1525  /**
1526   * Setter for property dataSourceChangeListener.
1527   * @param dataSourceChangeListener New value of property dataSourceChangeListener.
1528   */

1529  public void setDataSourceChangeListener(String JavaDoc dataSourceChangeListener) {
1530      this.dataSourceChangeListener = dataSourceChangeListener;
1531  }
1532
1533  /**
1534   * Rewrites the given MDX query with a generic version
1535   *
1536   * @param queryString MDX query text
1537   * @return a {@link Result} object
1538   */

1539    protected mondrian.olap.Query rewriteMDXQuery(mondrian.olap.Query query) {
1540        try {
1541            QueryVisitor visitor = new QueryVisitor();
1542            QueryAxis[] axes = query.getAxes();
1543            for (int i = 0; i < axes.length; i++) {
1544                axes[i].accept(visitor);
1545            }
1546
1547
1548            //
1549
// Cleans up the physical model for the known corner cases.
1550
// Case 1 : If the memberlist size = 1, then we should
1551
// not replace with logical version
1552
// eg: (Union((Union(Union(Crossjoin({
1553
// [Wholesaler].[All Wholesalers]},{[Product].[All Products].[Alleya]})
1554
// (there are no children).
1555
// In this case we need to retain the product name, even though
1556
// a logical version might have been used in the previous query
1557
//
1558
// Case 2: Within the same member list, if members are not
1559
// at the same level, remove the lowest members
1560
// eg: [Time].[2006].[q12006] and [Time].[Avg Sales]
1561
// are not same
1562
//
1563
String JavaDoc originalQuery = query.toString();
1564            if (logger.isDebugEnabled()) {
1565                logger.debug("rewriteMDXQuery: originalQuery="+originalQuery);
1566            }
1567            visitor.cleanUpPhysicalModel();
1568            removeLogicalNameFromList("[Time]");
1569
1570            boolean changeHappened = false;
1571            Iterator JavaDoc ite = visitor.getPhysicalModel().iterator();
1572            while (ite.hasNext()) {
1573                List JavaDoc list = (List JavaDoc) ite.next();
1574                Iterator JavaDoc memIterator = list.iterator();
1575
1576                loop:
1577                while (memIterator.hasNext()) {
1578                    String JavaDoc memberName =
1579                        ((mondrian.olap.Member)memIterator.next()).toString();
1580                    String JavaDoc[] uniqueNameParts = Util.explode(memberName);
1581                    String JavaDoc dimension = uniqueNameParts[0];
1582
1583                    Iterator JavaDoc logicalIt = aLogicalModel.iterator();
1584                    while (logicalIt.hasNext()) {
1585                        String JavaDoc logicalName = (String JavaDoc) logicalIt.next();
1586
1587                        // First see if the same dimension exists
1588
// between prev query and this query
1589
// If exists, then verify that they are
1590
// at the same level
1591
// Eg. [product].[All products] is not at
1592
// the same level of [product].[All products].
1593
// [abc] whereas [product].[All products].children iss.
1594

1595                        if (logicalName.startsWith(dimension) &&
1596                                isAtSameLevel(memberName, logicalName) ) {
1597                            originalQuery = replaceEnumeratedQuery(
1598                                             originalQuery,
1599                                             memberName,
1600                                             logicalName);
1601                            changeHappened = true;
1602//System.out.println("After replaceEnumeratedQuery originalQuery="+originalQuery);
1603
break loop;
1604                        }
1605                    }
1606                    // Match Not found.
1607
// That means there is no logical version exists for this
1608
// dimension
1609
break loop;
1610                }
1611            }
1612
1613            mondrian.olap.Query result = query;
1614
1615            if (changeHappened) {
1616                // Should we need to parse again? This is required because we
1617
// have rewritten the query we have to parse it again to store
1618
// the current logical model so that it can be used next time.
1619
result = getConnection().parseQuery(originalQuery);
1620                visitor = new QueryVisitor();
1621                axes = result.getAxes();
1622                for (int i = 0; i < axes.length; i++) {
1623                    axes[i].accept(visitor);
1624                }
1625            }
1626
1627            aLogicalModel = visitor.getLogicalModel();
1628
1629            return result;
1630
1631        } catch (Exception JavaDoc e) { // SHOULD NOT HAPPEN
1632
logger.warn(" Error in rewriting MDX Query " );
1633            e.printStackTrace();
1634            aLogicalModel.clear();
1635
1636            // In case of error, return the JPivot generated query
1637
return query;
1638        } finally {
1639            // empty
1640
}
1641    }
1642
1643
1644     /**
1645     * Converts a Enumerated MDX with logical one
1646     * The enumerated list will always be present with in "{}"
1647     * Find the first occurrence of the given member in the query
1648     * and replace everything with the logicalName until we find "}"
1649     * @param result
1650     * @return String version of mondrian Result object.
1651     */

1652    public String JavaDoc replaceEnumeratedQuery(final String JavaDoc query,
1653                                         final String JavaDoc memberName,
1654                                         final String JavaDoc logicalName) {
1655        if (logger.isDebugEnabled()) {
1656            logger.debug("replaceEnumeratedQuery: Given Query is " + query);
1657        }
1658
1659        StringBuffer JavaDoc result = new StringBuffer JavaDoc(200);
1660
1661        int index = query.indexOf(memberName);
1662        // first occurance of enum
1663
result.append(query.substring(0, index));
1664        // append with logical expr
1665
result.append(logicalName);
1666
1667        index = query.indexOf("}", index);
1668        //remaining
1669
result.append(query.substring(index));
1670
1671        if (logger.isDebugEnabled()) {
1672            logger.debug("replaceEnumeratedQuery: Resultant Query is " +
1673                result.toString());
1674        }
1675
1676        return result.toString();
1677    }
1678
1679    /**
1680     * Checks if 2 members are at the same level by comparing the
1681     * no. of "." [dots] present
1682     *
1683     * @param result
1684     * @return String version of mondrian Result object.
1685     */

1686    public boolean isAtSameLevel(final String JavaDoc memberName,
1687                                 final String JavaDoc logicalName) {
1688        final String JavaDoc[] memberUniqueNameParts = Util.explode(memberName);
1689        final String JavaDoc[] logicalUniqueNameParts = Util.explode(logicalName);
1690        return (memberUniqueNameParts.length == logicalUniqueNameParts.length);
1691
1692    }
1693
1694    /**
1695     * Visitor which builds a separate lists for logical members and
1696     * physical(enumerated) members
1697     */

1698    static class QueryVisitor extends MdxVisitorImpl {
1699
1700        // we need quick remove in method removeLogicalNameFromList()
1701
// which is why this is a LinkedList.
1702
private LinkedList JavaDoc logicalDimensionList;
1703        private List JavaDoc physicalDimensionList;
1704        private List JavaDoc enumMemberList;
1705
1706        private QueryVisitor() {
1707            this.logicalDimensionList = new LinkedList JavaDoc();
1708            this.physicalDimensionList = new ArrayList JavaDoc();
1709            this.enumMemberList = new ArrayList JavaDoc();
1710
1711        }
1712        public Object JavaDoc visit(mondrian.mdx.UnresolvedFunCall call) {
1713
1714            if (call.getFunName().equalsIgnoreCase("children") ||
1715                    call.getFunName().equalsIgnoreCase("members" )) {
1716                logicalDimensionList.add(call.toString());
1717                enumMemberList = new ArrayList JavaDoc();
1718                physicalDimensionList.add(enumMemberList);
1719
1720            } else if (call.getFunName().equals("{}")) {
1721                enumMemberList = new ArrayList JavaDoc();
1722                physicalDimensionList.add(enumMemberList);
1723            }
1724
1725            return null;
1726        }
1727        public Object JavaDoc visit(mondrian.mdx.ResolvedFunCall call) {
1728
1729            if (call.getFunName().equalsIgnoreCase("children") ||
1730                    call.getFunName().equalsIgnoreCase("members" )) {
1731
1732                logicalDimensionList.add(call.toString());
1733                enumMemberList = new ArrayList JavaDoc();
1734                physicalDimensionList.add(enumMemberList);
1735
1736            } else if (call.getFunName().equals("{}")) {
1737                enumMemberList = new ArrayList JavaDoc();
1738                physicalDimensionList.add(enumMemberList);
1739            }
1740
1741            return null;
1742        }
1743        public Object JavaDoc visit(mondrian.mdx.MemberExpr memberExpr) {
1744            mondrian.olap.Member member = memberExpr.getMember();
1745            enumMemberList.add(member);
1746
1747            return null;
1748        }
1749        public LinkedList JavaDoc getLogicalModel() {
1750            return logicalDimensionList;
1751        }
1752
1753        public List JavaDoc getPhysicalModel() {
1754            return physicalDimensionList;
1755        }
1756
1757        /**
1758         * Cleans up the physical model for the known corner cases.
1759         * Case 1 : If the memberlist size = 1, then we should not
1760         * replace with logical version
1761         * eg: (Union((Union(Union(Crossjoin({
1762         * [Wholesaler].[All Wholesalers]},{[Product].[All Products].[Alleya]})
1763         * In this case we need to retain the product name, even though
1764         * a logical version used in the prev query
1765         *
1766         * Case 2: Within the same member list, if members are not
1767         * at the same level, remove the lowest members
1768         * eg: [Time].[2006].[q12006] and [Time].[Avg Sales] are not same
1769         * @param result
1770         * @return String version of mondrian Result object.
1771         */

1772        public void cleanUpPhysicalModel() {
1773            List JavaDoc newList = new ArrayList JavaDoc();
1774            // TODO: use iterator
1775
for(int i = 0; i < physicalDimensionList.size(); i++) {
1776                List JavaDoc aList = (List JavaDoc) physicalDimensionList.get(i);
1777                if (aList.size() != 1 && allSameDimension(aList)) {
1778                    newList.add(aList);
1779                }
1780            }
1781            physicalDimensionList = newList;
1782        }
1783
1784
1785        // debug out
1786
public void print(Object JavaDoc msg) {
1787            System.out.println(msg);
1788        }
1789        // debug out
1790
public void printLists() {
1791           Iterator JavaDoc it = logicalDimensionList.iterator();
1792           while(it.hasNext()) {
1793              print((String JavaDoc)it.next());
1794           }
1795           print(" going to print members ");
1796           it = physicalDimensionList.iterator();
1797           while(it.hasNext()) {
1798               List JavaDoc list = (List JavaDoc)it.next();
1799               Iterator JavaDoc ite = list.iterator();
1800               while (ite.hasNext()) {
1801                   print((mondrian.olap.Member)ite.next());
1802               }
1803               print(" Going to print next member lists");
1804           }
1805        }
1806
1807        /**
1808         * As an example, in a member List get the first Member's
1809         * hierarchy name and then make sure that all of the rest
1810         * are also part of the same hierarchy.
1811         *
1812         * @param list
1813         * @return
1814         */

1815        private boolean allSameDimension(List JavaDoc list) {
1816            if (list.size() == 0) {
1817                return false;
1818            }
1819
1820            // Get the hierarchy name
1821
String JavaDoc firstMemberName =
1822                ((mondrian.olap.Member) list.get(0)).toString();
1823            String JavaDoc[] uniqueNameParts = Util.explode(firstMemberName);
1824            //String dimName = firstDim.substring(0, firstDim.indexOf("."));
1825
String JavaDoc dimName = uniqueNameParts[0];
1826            boolean result = true;
1827            Iterator JavaDoc it = list.iterator();
1828            while (it.hasNext()) {
1829                // see if member starts with the same name
1830
String JavaDoc name = ((mondrian.olap.Member) it.next()).toString();
1831                if (! name.startsWith(dimName)) {
1832                    result = false;
1833                    break;
1834                }
1835            }
1836
1837            return result;
1838        }
1839    }
1840
1841  /**
1842   * sets the dirty hierarchy whose logical representation needs to be removed
1843   */

1844  protected void removeLogicalNameFromList(final String JavaDoc hierName) {
1845      for (int i = 0; i < aLogicalModel.size(); i++) {
1846          String JavaDoc listName = (String JavaDoc) aLogicalModel.get(i);
1847          if (listName.startsWith(hierName)) {
1848              aLogicalModel.remove(i);
1849              break;
1850          }
1851      }
1852  }
1853  class Listener implements MemoryMonitor.Listener {
1854    String JavaDoc oomMsg;
1855    Listener() {
1856    }
1857    public void memoryUsageNotification(long used, long max) {
1858      StringBuffer JavaDoc buf = new StringBuffer JavaDoc(200);
1859      buf.append("OutOfMemory used=");
1860      buf.append(used);
1861      buf.append(", max=");
1862      buf.append(max);
1863      buf.append(" for mdx: ");
1864      buf.append(queryAdapter.getMonQuery().toMdx());
1865      this.oomMsg = buf.toString();
1866    }
1867    final void check() throws MemoryLimitExceededException {
1868      if (oomMsg != null) {
1869        throw new MemoryLimitExceededException(oomMsg);
1870      }
1871    }
1872  }
1873
1874
1875} // End MondrianModel
1876

1877
1878
Popular Tags