001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2003-2005 Julian Hyde 008// Copyright (C) 2005-2011 Pentaho 009// All Rights Reserved. 010*/ 011package mondrian.xmla; 012 013import org.apache.log4j.Logger; 014 015import org.w3c.dom.Element; 016 017import java.io.IOException; 018import java.io.UnsupportedEncodingException; 019import java.util.*; 020import javax.servlet.ServletConfig; 021import javax.servlet.ServletException; 022import javax.servlet.http.*; 023 024/** 025 * Base XML/A servlet. 026 * 027 * @author Gang Chen 028 * @since December, 2005 029 */ 030public abstract class XmlaServlet 031 extends HttpServlet 032 implements XmlaConstants 033{ 034 protected static final Logger LOGGER = Logger.getLogger(XmlaServlet.class); 035 036 public static final String PARAM_DATASOURCES_CONFIG = "DataSourcesConfig"; 037 public static final String PARAM_OPTIONAL_DATASOURCE_CONFIG = 038 "OptionalDataSourceConfig"; 039 public static final String PARAM_CHAR_ENCODING = "CharacterEncoding"; 040 public static final String PARAM_CALLBACKS = "Callbacks"; 041 042 protected XmlaHandler xmlaHandler = null; 043 protected String charEncoding = null; 044 private final List<XmlaRequestCallback> callbackList = 045 new ArrayList<XmlaRequestCallback>(); 046 047 private XmlaHandler.ConnectionFactory connectionFactory; 048 049 public enum Phase { 050 VALIDATE_HTTP_HEAD, 051 INITIAL_PARSE, 052 CALLBACK_PRE_ACTION, 053 PROCESS_HEADER, 054 PROCESS_BODY, 055 CALLBACK_POST_ACTION, 056 SEND_RESPONSE, 057 SEND_ERROR 058 } 059 060 /** 061 * Returns true if paramName's value is not null and 'true'. 062 */ 063 public static boolean getBooleanInitParameter( 064 ServletConfig servletConfig, 065 String paramName) 066 { 067 String paramValue = servletConfig.getInitParameter(paramName); 068 return paramValue != null && Boolean.valueOf(paramValue); 069 } 070 071 public static boolean getParameter( 072 HttpServletRequest req, 073 String paramName) 074 { 075 String paramValue = req.getParameter(paramName); 076 return paramValue != null && Boolean.valueOf(paramValue); 077 } 078 079 public XmlaServlet() { 080 } 081 082 083 /** 084 * Initializes servlet and XML/A handler. 085 * 086 */ 087 public void init(ServletConfig servletConfig) 088 throws ServletException 089 { 090 super.init(servletConfig); 091 092 // init: charEncoding 093 initCharEncodingHandler(servletConfig); 094 095 // init: callbacks 096 initCallbacks(servletConfig); 097 098 this.connectionFactory = createConnectionFactory(servletConfig); 099 } 100 101 protected abstract XmlaHandler.ConnectionFactory createConnectionFactory( 102 ServletConfig servletConfig) 103 throws ServletException; 104 105 /** 106 * Gets (creating if needed) the XmlaHandler. 107 * 108 * @return XMLA handler 109 */ 110 protected XmlaHandler getXmlaHandler() { 111 if (this.xmlaHandler == null) { 112 this.xmlaHandler = 113 new XmlaHandler( 114 connectionFactory, 115 "cxmla"); 116 } 117 return this.xmlaHandler; 118 } 119 120 /** 121 * Registers a callback. 122 */ 123 protected final void addCallback(XmlaRequestCallback callback) { 124 callbackList.add(callback); 125 } 126 127 /** 128 * Returns the list of callbacks. The list is immutable. 129 * 130 * @return list of callbacks 131 */ 132 protected final List<XmlaRequestCallback> getCallbacks() { 133 return Collections.unmodifiableList(callbackList); 134 } 135 136 /** 137 * Main entry for HTTP post method 138 * 139 */ 140 protected void doPost( 141 HttpServletRequest request, 142 HttpServletResponse response) 143 throws ServletException, IOException 144 { 145 // Request Soap Header and Body 146 // header [0] and body [1] 147 Element[] requestSoapParts = new Element[2]; 148 149 // Response Soap Header and Body 150 // An array allows response parts to be passed into callback 151 // and possible modifications returned. 152 // response header in [0] and response body in [1] 153 byte[][] responseSoapParts = new byte[2][]; 154 155 Phase phase = Phase.VALIDATE_HTTP_HEAD; 156 Enumeration.ResponseMimeType mimeType = 157 Enumeration.ResponseMimeType.SOAP; 158 159 try { 160 if (charEncoding != null) { 161 try { 162 request.setCharacterEncoding(charEncoding); 163 response.setCharacterEncoding(charEncoding); 164 } catch (UnsupportedEncodingException uee) { 165 charEncoding = null; 166 LOGGER.warn( 167 "Unsupported character encoding '" + charEncoding 168 + "': Use default character encoding from HTTP client " 169 + "for now"); 170 } 171 } 172 173 response.setContentType(mimeType.getMimeType()); 174 175 Map<String, Object> context = new HashMap<String, Object>(); 176 177 try { 178 if (LOGGER.isDebugEnabled()) { 179 LOGGER.debug("Invoking validate http header callbacks"); 180 } 181 for (XmlaRequestCallback callback : getCallbacks()) { 182 if (!callback.processHttpHeader( 183 request, 184 response, 185 context)) 186 { 187 return; 188 } 189 } 190 } catch (XmlaException xex) { 191 LOGGER.error( 192 "Errors when invoking callbacks validateHttpHeader", xex); 193 handleFault(response, responseSoapParts, phase, xex); 194 phase = Phase.SEND_ERROR; 195 marshallSoapMessage(response, responseSoapParts, mimeType); 196 return; 197 } catch (Exception ex) { 198 LOGGER.error( 199 "Errors when invoking callbacks validateHttpHeader", ex); 200 handleFault( 201 response, responseSoapParts, 202 phase, 203 new XmlaException( 204 SERVER_FAULT_FC, 205 CHH_CODE, 206 CHH_FAULT_FS, 207 ex)); 208 phase = Phase.SEND_ERROR; 209 marshallSoapMessage(response, responseSoapParts, mimeType); 210 return; 211 } 212 213 214 phase = Phase.INITIAL_PARSE; 215 216 try { 217 if (LOGGER.isDebugEnabled()) { 218 LOGGER.debug("Unmarshalling SOAP message"); 219 } 220 221 // check request's content type 222 String contentType = request.getContentType(); 223 if (contentType == null 224 || !contentType.contains("text/xml")) 225 { 226 throw new IllegalArgumentException( 227 "Only accepts content type 'text/xml', not '" 228 + contentType + "'"); 229 } 230 231 // are they asking for a JSON response? 232 String accept = request.getHeader("Accept"); 233 if (accept != null) { 234 mimeType = XmlaUtil.chooseResponseMimeType(accept); 235 if (mimeType == null) { 236 throw new IllegalArgumentException( 237 "Accept header '" + accept + "' is not a supported" 238 + " response content type. Allowed values:" 239 + " text/xml, application/xml, application/json."); 240 } 241 if (mimeType != Enumeration.ResponseMimeType.SOAP) { 242 response.setContentType(mimeType.getMimeType()); 243 } 244 } 245 context.put(CONTEXT_MIME_TYPE, mimeType); 246 247 unmarshallSoapMessage(request, requestSoapParts); 248 } catch (XmlaException xex) { 249 LOGGER.error("Unable to unmarshall SOAP message", xex); 250 handleFault(response, responseSoapParts, phase, xex); 251 phase = Phase.SEND_ERROR; 252 marshallSoapMessage(response, responseSoapParts, mimeType); 253 return; 254 } 255 256 phase = Phase.PROCESS_HEADER; 257 258 try { 259 if (LOGGER.isDebugEnabled()) { 260 LOGGER.debug("Handling XML/A message header"); 261 } 262 263 // process application specified SOAP header here 264 handleSoapHeader( 265 response, 266 requestSoapParts, 267 responseSoapParts, 268 context); 269 } catch (XmlaException xex) { 270 LOGGER.error("Errors when handling XML/A message", xex); 271 handleFault(response, responseSoapParts, phase, xex); 272 phase = Phase.SEND_ERROR; 273 marshallSoapMessage(response, responseSoapParts, mimeType); 274 return; 275 } 276 277 phase = Phase.CALLBACK_PRE_ACTION; 278 279 280 try { 281 if (LOGGER.isDebugEnabled()) { 282 LOGGER.debug("Invoking callbacks preAction"); 283 } 284 285 for (XmlaRequestCallback callback : getCallbacks()) { 286 callback.preAction(request, requestSoapParts, context); 287 } 288 } catch (XmlaException xex) { 289 LOGGER.error("Errors when invoking callbacks preaction", xex); 290 handleFault(response, responseSoapParts, phase, xex); 291 phase = Phase.SEND_ERROR; 292 marshallSoapMessage(response, responseSoapParts, mimeType); 293 return; 294 } catch (Exception ex) { 295 LOGGER.error("Errors when invoking callbacks preaction", ex); 296 handleFault( 297 response, responseSoapParts, 298 phase, 299 new XmlaException( 300 SERVER_FAULT_FC, 301 CPREA_CODE, 302 CPREA_FAULT_FS, 303 ex)); 304 phase = Phase.SEND_ERROR; 305 marshallSoapMessage(response, responseSoapParts, mimeType); 306 return; 307 } 308 309 phase = Phase.PROCESS_BODY; 310 311 try { 312 if (LOGGER.isDebugEnabled()) { 313 LOGGER.debug("Handling XML/A message body"); 314 } 315 316 // process XML/A request 317 handleSoapBody( 318 response, 319 requestSoapParts, 320 responseSoapParts, 321 context); 322 } catch (XmlaException xex) { 323 LOGGER.error("Errors when handling XML/A message", xex); 324 handleFault(response, responseSoapParts, phase, xex); 325 phase = Phase.SEND_ERROR; 326 marshallSoapMessage(response, responseSoapParts, mimeType); 327 return; 328 } 329 330 mimeType = 331 (Enumeration.ResponseMimeType) context.get(CONTEXT_MIME_TYPE); 332 333 phase = Phase.CALLBACK_POST_ACTION; 334 335 try { 336 if (LOGGER.isDebugEnabled()) { 337 LOGGER.debug("Invoking callbacks postAction"); 338 } 339 340 for (XmlaRequestCallback callback : getCallbacks()) { 341 callback.postAction( 342 request, response, 343 responseSoapParts, context); 344 } 345 } catch (XmlaException xex) { 346 LOGGER.error("Errors when invoking callbacks postaction", xex); 347 handleFault(response, responseSoapParts, phase, xex); 348 phase = Phase.SEND_ERROR; 349 marshallSoapMessage(response, responseSoapParts, mimeType); 350 return; 351 } catch (Exception ex) { 352 LOGGER.error("Errors when invoking callbacks postaction", ex); 353 handleFault( 354 response, 355 responseSoapParts, 356 phase, 357 new XmlaException( 358 SERVER_FAULT_FC, 359 CPOSTA_CODE, 360 CPOSTA_FAULT_FS, 361 ex)); 362 phase = Phase.SEND_ERROR; 363 marshallSoapMessage(response, responseSoapParts, mimeType); 364 return; 365 } 366 367 phase = Phase.SEND_RESPONSE; 368 369 try { 370 response.setStatus(HttpServletResponse.SC_OK); 371 marshallSoapMessage(response, responseSoapParts, mimeType); 372 } catch (XmlaException xex) { 373 LOGGER.error("Errors when handling XML/A message", xex); 374 handleFault(response, responseSoapParts, phase, xex); 375 phase = Phase.SEND_ERROR; 376 marshallSoapMessage(response, responseSoapParts, mimeType); 377 } 378 } catch (Throwable t) { 379 LOGGER.error("Unknown Error when handling XML/A message", t); 380 handleFault(response, responseSoapParts, phase, t); 381 marshallSoapMessage(response, responseSoapParts, mimeType); 382 } 383 } 384 385 /** 386 * Implement to provide application specified SOAP unmarshalling algorithm. 387 */ 388 protected abstract void unmarshallSoapMessage( 389 HttpServletRequest request, 390 Element[] requestSoapParts) 391 throws XmlaException; 392 393 /** 394 * Implement to handle application specified SOAP header. 395 */ 396 protected abstract void handleSoapHeader( 397 HttpServletResponse response, 398 Element[] requestSoapParts, 399 byte[][] responseSoapParts, 400 Map<String, Object> context) 401 throws XmlaException; 402 403 /** 404 * Implement to handle XML/A request. 405 */ 406 protected abstract void handleSoapBody( 407 HttpServletResponse response, 408 Element[] requestSoapParts, 409 byte[][] responseSoapParts, 410 Map<String, Object> context) 411 throws XmlaException; 412 413 /** 414 * Implement to provide application specified SOAP marshalling algorithm. 415 */ 416 protected abstract void marshallSoapMessage( 417 HttpServletResponse response, 418 byte[][] responseSoapParts, 419 Enumeration.ResponseMimeType responseMimeType) 420 throws XmlaException; 421 422 /** 423 * Implement to application specified handler of SOAP fualt. 424 */ 425 protected abstract void handleFault( 426 HttpServletResponse response, 427 byte[][] responseSoapParts, 428 Phase phase, 429 Throwable t); 430 431 /** 432 * Initialize character encoding 433 */ 434 protected void initCharEncodingHandler(ServletConfig servletConfig) { 435 String paramValue = servletConfig.getInitParameter(PARAM_CHAR_ENCODING); 436 if (paramValue != null) { 437 this.charEncoding = paramValue; 438 } else { 439 this.charEncoding = null; 440 LOGGER.warn("Use default character encoding from HTTP client"); 441 } 442 } 443 444 /** 445 * Registers callbacks configured in web.xml. 446 */ 447 protected void initCallbacks(ServletConfig servletConfig) { 448 String callbacksValue = servletConfig.getInitParameter(PARAM_CALLBACKS); 449 450 if (callbacksValue != null) { 451 String[] classNames = callbacksValue.split(";"); 452 453 int count = 0; 454 nextCallback: 455 for (String className1 : classNames) { 456 String className = className1.trim(); 457 458 try { 459 Class<?> cls = Class.forName(className); 460 if (XmlaRequestCallback.class.isAssignableFrom(cls)) { 461 XmlaRequestCallback callback = 462 (XmlaRequestCallback) cls.newInstance(); 463 464 try { 465 callback.init(servletConfig); 466 } catch (Exception e) { 467 LOGGER.warn( 468 "Failed to initialize callback '" 469 + className + "'", 470 e); 471 continue nextCallback; 472 } 473 474 addCallback(callback); 475 count++; 476 477 if (LOGGER.isDebugEnabled()) { 478 LOGGER.debug( 479 "Register callback '" + className + "'"); 480 } 481 } else { 482 LOGGER.warn( 483 "'" + className + "' is not an implementation of '" 484 + XmlaRequestCallback.class + "'"); 485 } 486 } catch (ClassNotFoundException cnfe) { 487 LOGGER.warn( 488 "Callback class '" + className + "' not found", 489 cnfe); 490 } catch (InstantiationException ie) { 491 LOGGER.warn( 492 "Can't instantiate class '" + className + "'", 493 ie); 494 } catch (IllegalAccessException iae) { 495 LOGGER.warn( 496 "Can't instantiate class '" + className + "'", 497 iae); 498 } 499 } 500 LOGGER.debug( 501 "Registered " + count + " callback" + (count > 1 ? "s" : "")); 502 } 503 } 504} 505 506// End XmlaServlet.java