001 /* 002 // $Id: Query.java 292 2009-11-23 00:58:06Z jhyde $ 003 // This software is subject to the terms of the Eclipse Public License v1.0 004 // Agreement, available at the following URL: 005 // http://www.eclipse.org/legal/epl-v10.html. 006 // Copyright (C) 2007-2009 Julian Hyde 007 // All Rights Reserved. 008 // You must accept the terms of that agreement to use this software. 009 */ 010 package org.olap4j.query; 011 012 import org.olap4j.metadata.*; 013 import org.olap4j.*; 014 import org.olap4j.mdx.SelectNode; 015 016 import java.util.*; 017 import java.util.Map.Entry; 018 import java.sql.SQLException; 019 020 /** 021 * Base query model object. 022 * 023 * @author jhyde, jdixon, Luc Boudreau 024 * @version $Id: Query.java 292 2009-11-23 00:58:06Z jhyde $ 025 * @since May 29, 2007 026 */ 027 public class Query extends QueryNodeImpl { 028 029 protected final String name; 030 protected Map<Axis, QueryAxis> axes = new HashMap<Axis, QueryAxis>(); 031 protected QueryAxis across; 032 protected QueryAxis down; 033 protected QueryAxis filter; 034 protected QueryAxis unused; 035 protected final Cube cube; 036 protected Map<String, QueryDimension> dimensionMap = 037 new HashMap<String, QueryDimension>(); 038 /** 039 * Whether or not to select the default hierarchy and default 040 * member on a dimension if no explicit selections were performed. 041 */ 042 protected boolean selectDefaultMembers = true; 043 private final OlapConnection connection; 044 private final SelectionFactory selectionFactory = new SelectionFactory(); 045 046 /** 047 * Constructs a Query object. 048 * @param name Any arbitrary name to give to this query. 049 * @param cube A Cube object against which to build a query. 050 * @throws SQLException If an error occurs while accessing the 051 * cube's underlying connection. 052 */ 053 public Query(String name, Cube cube) throws SQLException { 054 super(); 055 this.name = name; 056 this.cube = cube; 057 final Catalog catalog = cube.getSchema().getCatalog(); 058 this.connection = 059 catalog.getMetaData().getConnection().unwrap(OlapConnection.class); 060 this.connection.setCatalog(catalog.getName()); 061 this.unused = new QueryAxis(this, null); 062 for (Dimension dimension : cube.getDimensions()) { 063 QueryDimension queryDimension = new QueryDimension( 064 this, dimension); 065 unused.getDimensions().add(queryDimension); 066 dimensionMap.put(queryDimension.getName(), queryDimension); 067 } 068 across = new QueryAxis(this, Axis.COLUMNS); 069 down = new QueryAxis(this, Axis.ROWS); 070 filter = new QueryAxis(this, Axis.FILTER); 071 axes.put(null, unused); 072 axes.put(Axis.COLUMNS, across); 073 axes.put(Axis.ROWS, down); 074 axes.put(Axis.FILTER, filter); 075 } 076 077 /** 078 * Returns the MDX parse tree behind this Query. The returned object is 079 * generated for each call to this function. Altering the returned 080 * SelectNode object won't affect the query itself. 081 * @return A SelectNode object representing the current query structure. 082 */ 083 public SelectNode getSelect() { 084 return Olap4jNodeConverter.toOlap4j(this); 085 } 086 087 /** 088 * Returns the underlying cube object that is used to query against. 089 * @return The Olap4j's Cube object. 090 */ 091 public Cube getCube() { 092 return cube; 093 } 094 095 /** 096 * Returns the Olap4j's Dimension object according to the name 097 * given as a parameter. If no dimension of the given name is found, 098 * a null value will be returned. 099 * @param name The name of the dimension you want the object for. 100 * @return The dimension object, null if no dimension of that 101 * name can be found. 102 */ 103 public QueryDimension getDimension(String name) { 104 return dimensionMap.get(name); 105 } 106 107 /** 108 * Swaps rows and columns axes. Only applicable if there are two axes. 109 */ 110 public void swapAxes() { 111 // Only applicable if there are two axes - plus filter and unused. 112 if (axes.size() != 4) { 113 throw new IllegalArgumentException(); 114 } 115 List<QueryDimension> tmpAcross = new ArrayList<QueryDimension>(); 116 tmpAcross.addAll(across.getDimensions()); 117 118 List<QueryDimension> tmpDown = new ArrayList<QueryDimension>(); 119 tmpDown.addAll(down.getDimensions()); 120 121 across.getDimensions().clear(); 122 Map<Integer, QueryNode> acrossChildList = 123 new HashMap<Integer, QueryNode>(); 124 for (int cpt = 0; cpt < tmpAcross.size();cpt++) { 125 acrossChildList.put(Integer.valueOf(cpt), tmpAcross.get(cpt)); 126 } 127 across.notifyRemove(acrossChildList); 128 129 down.getDimensions().clear(); 130 Map<Integer, QueryNode> downChildList = 131 new HashMap<Integer, QueryNode>(); 132 for (int cpt = 0; cpt < tmpDown.size();cpt++) { 133 downChildList.put(Integer.valueOf(cpt), tmpDown.get(cpt)); 134 } 135 down.notifyRemove(downChildList); 136 137 across.getDimensions().addAll(tmpDown); 138 across.notifyAdd(downChildList); 139 140 down.getDimensions().addAll(tmpAcross); 141 down.notifyAdd(acrossChildList); 142 } 143 144 public QueryAxis getAxis(Axis axis) { 145 return this.axes.get(axis); 146 } 147 148 /** 149 * Returns a map of the current query's axis. 150 * <p>Be aware that modifications to this list might 151 * have unpredictable consequences.</p> 152 * @return A standard Map object that represents the 153 * current query's axis. 154 */ 155 public Map<Axis, QueryAxis> getAxes() { 156 return axes; 157 } 158 159 /** 160 * Returns the fictional axis into which all unused dimensions are stored. 161 * All dimensions included in this axis will not be part of the query. 162 * @return The QueryAxis representing dimensions that are currently not 163 * used inside the query. 164 */ 165 public QueryAxis getUnusedAxis() { 166 return unused; 167 } 168 169 /** 170 * Safely disposes of all underlying objects of this 171 * query. 172 * @param closeConnection Whether or not to call the 173 * {@link OlapConnection#close()} method of the underlying 174 * connection. 175 */ 176 public void tearDown(boolean closeConnection) { 177 for (Entry<Axis, QueryAxis> entry : this.axes.entrySet()) { 178 entry.getValue().tearDown(); 179 } 180 this.axes.clear(); 181 this.clearListeners(); 182 if (closeConnection) { 183 try { 184 this.connection.close(); 185 } catch (SQLException e) { 186 e.printStackTrace(); 187 } 188 } 189 } 190 191 /** 192 * Safely disposes of all underlying objects of this 193 * query and closes the underlying {@link OlapConnection}. 194 * <p>Equivalent of calling Query.tearDown(true). 195 */ 196 public void tearDown() { 197 this.tearDown(true); 198 } 199 200 /** 201 * Validates the current query structure. If a dimension axis has 202 * been placed on an axis but no selections were performed on it, 203 * the default hierarchy and default member will be selected. This 204 * can be turned off by invoking the 205 * {@link Query#setSelectDefaultMembers(boolean)} method. 206 * @throws OlapException If the query is not valid, an exception 207 * will be thrown and it's message will describe exactly what to fix. 208 */ 209 public void validate() throws OlapException { 210 try { 211 // First, perform default selections if needed. 212 if (this.selectDefaultMembers) { 213 // Perform default selection on the dimensions on the rows axis. 214 for (QueryDimension dimension : this.getAxis(Axis.ROWS) 215 .getDimensions()) 216 { 217 if (dimension.getInclusions().size() == 0) { 218 Member defaultMember = dimension.getDimension() 219 .getDefaultHierarchy().getDefaultMember(); 220 dimension.include(defaultMember); 221 } 222 } 223 // Perform default selection on the 224 // dimensions on the columns axis. 225 for (QueryDimension dimension : this.getAxis(Axis.COLUMNS) 226 .getDimensions()) 227 { 228 if (dimension.getInclusions().size() == 0) { 229 Member defaultMember = dimension.getDimension() 230 .getDefaultHierarchy().getDefaultMember(); 231 dimension.include(defaultMember); 232 } 233 } 234 // Perform default selection on the dimensions 235 // on the filter axis. 236 for (QueryDimension dimension : this.getAxis(Axis.FILTER) 237 .getDimensions()) 238 { 239 if (dimension.getInclusions().size() == 0) { 240 Member defaultMember = dimension.getDimension() 241 .getDefaultHierarchy().getDefaultMember(); 242 dimension.include(defaultMember); 243 } 244 } 245 } 246 247 // We at least need a dimension on the rows and on the columns axis. 248 if (this.getAxis(Axis.ROWS).getDimensions().size() == 0) { 249 throw new OlapException( 250 "A valid Query requires at least one dimension on the rows axis."); 251 } 252 if (this.getAxis(Axis.COLUMNS).getDimensions().size() == 0) { 253 throw new OlapException( 254 "A valid Query requires at least one dimension on the columns axis."); 255 } 256 257 // Try to build a select tree. 258 this.getSelect(); 259 } catch (Exception e) { 260 throw new OlapException("Query validation failed.", e); 261 } 262 } 263 264 /** 265 * Executes the query against the current OlapConnection and returns 266 * a CellSet object representation of the data. 267 * 268 * @return A proper CellSet object that represents the query execution 269 * results. 270 * @throws OlapException If something goes sour, an OlapException will 271 * be thrown to the caller. It could be caused by many things, like 272 * a stale connection. Look at the root cause for more details. 273 */ 274 public CellSet execute() throws OlapException { 275 SelectNode mdx = getSelect(); 276 final Catalog catalog = cube.getSchema().getCatalog(); 277 try { 278 this.connection.setCatalog(catalog.getName()); 279 } catch (SQLException e) { 280 throw new OlapException("Error while executing query", e); 281 } 282 OlapStatement olapStatement = connection.createStatement(); 283 return olapStatement.executeOlapQuery(mdx); 284 } 285 286 /** 287 * Returns this query's name. There is no guarantee that it is unique 288 * and is set at object instanciation. 289 * @return This query's name. 290 */ 291 public String getName() { 292 return name; 293 } 294 295 /** 296 * Returns the current locale with which this query is expressed. 297 * @return A standard Locale object. 298 */ 299 public Locale getLocale() { 300 // REVIEW Do queries really support locales? 301 return Locale.getDefault(); 302 } 303 304 /** 305 * Package restricted method to access this query's selection factory. 306 * Usually used by query dimensions who wants to perform selections. 307 * @return The underlying SelectionFactory implementation. 308 */ 309 SelectionFactory getSelectionFactory() { 310 return selectionFactory; 311 } 312 313 /** 314 * Behavior setter for a query. By default, if a dimension is placed on 315 * an axis but no selections are made, the default hierarchy and 316 * the default member will be selected when validating the query. 317 * This behavior can be turned off by this setter. 318 * @param selectDefaultMembers Enables or disables the default 319 * member and hierarchy selection upon validation. 320 */ 321 public void setSelectDefaultMembers(boolean selectDefaultMembers) { 322 this.selectDefaultMembers = selectDefaultMembers; 323 } 324 } 325 326 // End Query.java