KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > mondrian > xmla > XmlaServlet


1 /*
2 // $Id: //open/mondrian/src/main/mondrian/xmla/XmlaServlet.java#24 $
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-2007 Julian Hyde
7 // All Rights Reserved.
8 // You must accept the terms of that agreement to use this software.
9 //
10 // jhyde, May 2, 2003
11 */

12 package mondrian.xmla;
13
14 import mondrian.olap.Util;
15 import mondrian.spi.CatalogLocator;
16 import mondrian.spi.impl.ServletContextCatalogLocator;
17
18 import org.apache.log4j.Logger;
19 import org.eigenbase.xom.*;
20 import org.w3c.dom.Element JavaDoc;
21
22 import javax.servlet.ServletContext JavaDoc;
23 import javax.servlet.ServletConfig JavaDoc;
24 import javax.servlet.ServletException JavaDoc;
25 import javax.servlet.http.HttpServlet JavaDoc;
26 import javax.servlet.http.HttpServletRequest JavaDoc;
27 import javax.servlet.http.HttpServletResponse JavaDoc;
28 import java.io.*;
29 import java.net.MalformedURLException JavaDoc;
30 import java.net.URL JavaDoc;
31 import java.util.*;
32
33 /**
34  * Base XML/A servlet.
35  *
36  * @author Gang Chen
37  * @since December, 2005
38  * @version $Id: //open/mondrian/src/main/mondrian/xmla/XmlaServlet.java#24 $
39  */

40 public abstract class XmlaServlet extends HttpServlet JavaDoc
41                                   implements XmlaConstants {
42
43     private static final Logger LOGGER = Logger.getLogger(XmlaServlet.class);
44
45     public static final String JavaDoc PARAM_DATASOURCES_CONFIG = "DataSourcesConfig";
46     public static final String JavaDoc PARAM_OPTIONAL_DATASOURCE_CONFIG =
47             "OptionalDataSourceConfig";
48     public static final String JavaDoc PARAM_CHAR_ENCODING = "CharacterEncoding";
49     public static final String JavaDoc PARAM_CALLBACKS = "Callbacks";
50
51     public static final String JavaDoc DEFAULT_DATASOURCE_FILE = "datasources.xml";
52
53     public enum Phase {
54         VALIDATE_HTTP_HEAD,
55         INITIAL_PARSE,
56         CALLBACK_PRE_ACTION,
57         PROCESS_HEADER,
58         PROCESS_BODY,
59         CALLBACK_POST_ACTION,
60         SEND_RESPONSE,
61         SEND_ERROR
62     }
63
64     /**
65      * If paramName's value is not null and 'true', then return true.
66      *
67      */

68     public static boolean getBooleanInitParameter(
69             ServletConfig JavaDoc servletConfig,
70             String JavaDoc paramName) {
71         String JavaDoc paramValue = servletConfig.getInitParameter(paramName);
72         return paramValue != null && Boolean.valueOf(paramValue);
73     }
74
75     public static boolean getParameter(
76             HttpServletRequest JavaDoc req,
77             String JavaDoc paramName) {
78         String JavaDoc paramValue = req.getParameter(paramName);
79         return paramValue != null && Boolean.valueOf(paramValue);
80     }
81
82     protected CatalogLocator catalogLocator = null;
83     protected DataSourcesConfig.DataSources dataSources = null;
84     protected XmlaHandler xmlaHandler = null;
85     protected String JavaDoc charEncoding = null;
86     private final List<XmlaRequestCallback> callbackList =
87         new ArrayList<XmlaRequestCallback>();
88
89     public XmlaServlet() {
90     }
91
92
93     /**
94      * Initializes servlet and XML/A handler.
95      *
96      */

97     public void init(ServletConfig JavaDoc servletConfig) throws ServletException JavaDoc {
98         super.init(servletConfig);
99
100         // init: charEncoding
101
initCharEncodingHandler(servletConfig);
102
103         // init: callbacks
104
initCallbacks(servletConfig);
105
106         // make: catalogLocator
107
// A derived class can alter how the calalog locator object is
108
// created.
109
this.catalogLocator = makeCatalogLocator(servletConfig);
110
111         DataSourcesConfig.DataSources dataSources =
112                 makeDataSources(servletConfig);
113         addToDataSources(dataSources);
114     }
115
116     /**
117      * Gets (creating if needed) the XmlaHandler.
118      *
119      * @return XMLA handler
120      */

121     protected XmlaHandler getXmlaHandler() {
122         if (this.xmlaHandler == null) {
123             this.xmlaHandler =
124                 new XmlaHandler(this.dataSources, this.catalogLocator, "cxmla");
125         }
126         return this.xmlaHandler;
127     }
128
129     /**
130      * Registers a callback.
131      */

132     protected final void addCallback(XmlaRequestCallback callback) {
133         callbackList.add(callback);
134     }
135
136     /**
137      * Return an unmodifiable list of callbacks.
138      *
139      * @return
140      */

141     protected final List<XmlaRequestCallback> getCallbacks() {
142         return Collections.unmodifiableList(callbackList);
143     }
144
145
146     /**
147      * Main entry for HTTP post method
148      *
149      */

150     protected void doPost(
151             HttpServletRequest JavaDoc request,
152             HttpServletResponse JavaDoc response)
153             throws ServletException JavaDoc, IOException {
154
155         // Request Soap Header and Body
156
// header [0] and body [1]
157
Element JavaDoc[] requestSoapParts = new Element JavaDoc[2];
158
159         // Response Soap Header and Body
160
// An array allows response parts to be passed into callback
161
// and possible modifications returned.
162
// response header in [0] and response body in [1]
163
byte[][] responseSoapParts = new byte[2][];
164
165         Phase phase = Phase.VALIDATE_HTTP_HEAD;
166
167         try {
168
169             if (charEncoding != null) {
170                 try {
171                     request.setCharacterEncoding(charEncoding);
172                     response.setCharacterEncoding(charEncoding);
173                 } catch (UnsupportedEncodingException uee) {
174                     charEncoding = null;
175                     String JavaDoc msg = "Unsupported character encoding '" +
176                         charEncoding +
177                         "': " +
178                         "Use default character encoding from HTTP client for now";
179                     LOGGER.warn(msg);
180                 }
181             }
182
183             response.setContentType("text/xml");
184
185             Map<String JavaDoc, String JavaDoc> context = new HashMap<String JavaDoc, String JavaDoc>();
186
187             try {
188                 if (LOGGER.isDebugEnabled()) {
189                     LOGGER.debug("Invoking validate http header callbacks");
190                 }
191                 for (XmlaRequestCallback callback : getCallbacks()) {
192                     if (!callback.processHttpHeader(
193                         request,
194                         response,
195                         context)) {
196                         return;
197                     }
198                 }
199
200             } catch (XmlaException xex) {
201                 LOGGER.error("Errors when invoking callbacks validateHttpHeader", xex);
202                 handleFault(response, responseSoapParts, phase, xex);
203                 phase = Phase.SEND_ERROR;
204                 marshallSoapMessage(response, responseSoapParts);
205                 return;
206
207             } catch (Exception JavaDoc ex) {
208                 LOGGER.error("Errors when invoking callbacks validateHttpHeader", ex);
209                 handleFault(response, responseSoapParts,
210                         phase, new XmlaException(
211                                 SERVER_FAULT_FC,
212                                 CHH_CODE,
213                                 CHH_FAULT_FS,
214                                 ex));
215                 phase = Phase.SEND_ERROR;
216                 marshallSoapMessage(response, responseSoapParts);
217                 return;
218             }
219
220
221             phase = Phase.INITIAL_PARSE;
222
223             try {
224                 if (LOGGER.isDebugEnabled()) {
225                     LOGGER.debug("Unmarshalling SOAP message");
226                 }
227
228                 // check request's content type
229
String JavaDoc contentType = request.getContentType();
230                 if (contentType == null ||
231                     contentType.indexOf("text/xml") == -1) {
232                     throw new IllegalArgumentException JavaDoc("Only accepts content type 'text/xml', not '" + contentType + "'");
233                 }
234
235                 unmarshallSoapMessage(request, requestSoapParts);
236
237             } catch (XmlaException xex) {
238                 LOGGER.error("Unable to unmarshall SOAP message", xex);
239                 handleFault(response, responseSoapParts, phase, xex);
240                 phase = Phase.SEND_ERROR;
241                 marshallSoapMessage(response, responseSoapParts);
242                 return;
243             }
244
245             phase = Phase.PROCESS_HEADER;
246
247             try {
248                 if (LOGGER.isDebugEnabled()) {
249                     LOGGER.debug("Handling XML/A message header");
250                 }
251
252                 // process application specified SOAP header here
253
handleSoapHeader(response,
254                                  requestSoapParts,
255                                  responseSoapParts,
256                                  context);
257             } catch (XmlaException xex) {
258                 LOGGER.error("Errors when handling XML/A message", xex);
259                 handleFault(response, responseSoapParts, phase, xex);
260                 phase = Phase.SEND_ERROR;
261                 marshallSoapMessage(response, responseSoapParts);
262                 return;
263             }
264
265             phase = Phase.CALLBACK_PRE_ACTION;
266
267
268             try {
269                 if (LOGGER.isDebugEnabled()) {
270                     LOGGER.debug("Invoking callbacks preAction");
271                 }
272
273                 for (XmlaRequestCallback callback : getCallbacks()) {
274                     callback.preAction(request, requestSoapParts, context);
275                 }
276             } catch (XmlaException xex) {
277                 LOGGER.error("Errors when invoking callbacks preaction", xex);
278                 handleFault(response, responseSoapParts, phase, xex);
279                 phase = Phase.SEND_ERROR;
280                 marshallSoapMessage(response, responseSoapParts);
281                 return;
282
283             } catch (Exception JavaDoc ex) {
284                 LOGGER.error("Errors when invoking callbacks preaction", ex);
285                 handleFault(response, responseSoapParts,
286                         phase, new XmlaException(
287                                 SERVER_FAULT_FC,
288                                 CPREA_CODE,
289                                 CPREA_FAULT_FS,
290                                 ex));
291                 phase = Phase.SEND_ERROR;
292                 marshallSoapMessage(response, responseSoapParts);
293                 return;
294             }
295
296             phase = Phase.PROCESS_BODY;
297
298             try {
299                 if (LOGGER.isDebugEnabled()) {
300                     LOGGER.debug("Handling XML/A message body");
301                 }
302
303                 // process XML/A request
304
handleSoapBody(response,
305                                requestSoapParts,
306                                responseSoapParts,
307                                context);
308
309             } catch (XmlaException xex) {
310                 LOGGER.error("Errors when handling XML/A message", xex);
311                 handleFault(response, responseSoapParts, phase, xex);
312                 phase = Phase.SEND_ERROR;
313                 marshallSoapMessage(response, responseSoapParts);
314                 return;
315             }
316
317             phase = Phase.CALLBACK_POST_ACTION;
318
319             try {
320                 if (LOGGER.isDebugEnabled()) {
321                     LOGGER.debug("Invoking callbacks postAction");
322                 }
323
324                 for (XmlaRequestCallback callback : getCallbacks()) {
325                     callback.postAction(
326                         request, response,
327                         responseSoapParts, context);
328                 }
329             } catch (XmlaException xex) {
330                 LOGGER.error("Errors when invoking callbacks postaction", xex);
331                 handleFault(response, responseSoapParts, phase, xex);
332                 phase = Phase.SEND_ERROR;
333                 marshallSoapMessage(response, responseSoapParts);
334                 return;
335
336             } catch (Exception JavaDoc ex) {
337                 LOGGER.error("Errors when invoking callbacks postaction", ex);
338                 handleFault(response, responseSoapParts,
339                         phase, new XmlaException(
340                                 SERVER_FAULT_FC,
341                                 CPOSTA_CODE,
342                                 CPOSTA_FAULT_FS,
343                                 ex));
344                 phase = Phase.SEND_ERROR;
345                 marshallSoapMessage(response, responseSoapParts);
346                 return;
347             }
348
349             phase = Phase.SEND_RESPONSE;
350
351             try {
352
353                 response.setStatus(HttpServletResponse.SC_OK);
354                 marshallSoapMessage(response, responseSoapParts);
355
356             } catch (XmlaException xex) {
357                 LOGGER.error("Errors when handling XML/A message", xex);
358                 handleFault(response, responseSoapParts, phase, xex);
359                 phase = Phase.SEND_ERROR;
360                 marshallSoapMessage(response, responseSoapParts);
361                 return;
362             }
363
364         } catch (Throwable JavaDoc t) {
365             LOGGER.error("Unknown Error when handling XML/A message", t);
366             handleFault(response, responseSoapParts, phase, t);
367             marshallSoapMessage(response, responseSoapParts);
368         }
369
370     }
371
372     /**
373      * Implement to provide application specified SOAP unmarshalling algorithm.
374      *
375      * @return SOAP header, body as a tow items array.
376      */

377     protected abstract void unmarshallSoapMessage(
378             HttpServletRequest JavaDoc request,
379             Element JavaDoc[] requestSoapParts) throws XmlaException;
380
381     /**
382      * Implement to handle application specified SOAP header.
383      *
384      * @return if no header data in response, please return null or byte[0].
385      */

386     protected abstract void handleSoapHeader(
387             HttpServletResponse JavaDoc response,
388             Element JavaDoc[] requestSoapParts,
389             byte[][] responseSoapParts,
390             Map<String JavaDoc, String JavaDoc> context) throws XmlaException;
391
392     /**
393      * Implement to hanle XML/A request.
394      *
395      * @return XML/A response.
396      */

397     protected abstract void handleSoapBody(
398             HttpServletResponse JavaDoc response,
399             Element JavaDoc[] requestSoapParts,
400             byte[][] responseSoapParts,
401             Map<String JavaDoc, String JavaDoc> context) throws XmlaException;
402
403     /**
404      * Implement to privode application specified SOAP marshalling algorithm.
405      */

406     protected abstract void marshallSoapMessage(
407             HttpServletResponse JavaDoc response,
408             byte[][] responseSoapParts) throws XmlaException;
409
410     /**
411      * Implement to application specified handler of SOAP fualt.
412      */

413     protected abstract void handleFault(
414             HttpServletResponse JavaDoc response,
415             byte[][] responseSoapParts,
416             Phase phase,
417             Throwable JavaDoc t);
418
419
420
421     /**
422      * Make catalog locator. Derived classes can roll their own
423      */

424     protected CatalogLocator makeCatalogLocator(ServletConfig JavaDoc servletConfig) {
425         ServletContext JavaDoc servletContext = servletConfig.getServletContext();
426         return new ServletContextCatalogLocator(servletContext);
427     }
428
429     /**
430      * Make DataSourcesConfig.DataSources instance. Derived classes
431      * can roll their own
432      * <p>
433      * If there is an initParameter called "DataSourcesConfig"
434      * get its value, replace any "${key}" content with "value" where
435      * "key/value" are System properties, and try to create a URL
436      * instance out of it. If that fails, then assume its a
437      * real filepath and if the file exists then create a URL from it
438      * (but only if the file exists).
439      * If there is no initParameter with that name, then attempt to
440      * find the file called "datasources.xml" under "WEB-INF/"
441      * and if it exists, use it.
442      */

443     protected DataSourcesConfig.DataSources makeDataSources(
444                 ServletConfig JavaDoc servletConfig) {
445
446         String JavaDoc paramValue =
447                 servletConfig.getInitParameter(PARAM_DATASOURCES_CONFIG);
448         // if false, then do not throw exception if the file/url
449
// can not be found
450
boolean optional =
451             getBooleanInitParameter(servletConfig, PARAM_OPTIONAL_DATASOURCE_CONFIG);
452
453         URL JavaDoc dataSourcesConfigUrl = null;
454         try {
455             if (paramValue == null) {
456                 // fallback to default
457
String JavaDoc defaultDS = "WEB-INF/" + DEFAULT_DATASOURCE_FILE;
458                 ServletContext JavaDoc servletContext = servletConfig.getServletContext();
459                 File realPath = new File(servletContext.getRealPath(defaultDS));
460                 if (realPath.exists()) {
461                     // only if it exists
462
dataSourcesConfigUrl = realPath.toURL();
463                 }
464             } else {
465                 paramValue = Util.replaceProperties(
466                     paramValue,
467                     Util.toMap(System.getProperties()));
468                 if (LOGGER.isDebugEnabled()) {
469                     String JavaDoc msg = "XmlaServlet.makeDataSources: " +
470                             "paramValue="+paramValue;
471                     LOGGER.debug(msg);
472                 }
473                 // is the parameter a valid URL
474
MalformedURLException JavaDoc mue = null;
475                 try {
476                     dataSourcesConfigUrl = new URL JavaDoc(paramValue);
477                 } catch (MalformedURLException JavaDoc e) {
478                     // not a valid url
479
mue = e;
480                 }
481                 if (dataSourcesConfigUrl == null) {
482                     // see if its a full valid file path
483
File f = new File(paramValue);
484                     if (f.exists()) {
485                         // yes, a real file path
486
dataSourcesConfigUrl = f.toURL();
487                     } else if (mue != null) {
488                         // neither url or file,
489
// is it not optional
490
if (! optional) {
491                             throw mue;
492                         }
493                     }
494                 }
495             }
496         } catch (MalformedURLException JavaDoc mue) {
497             throw Util.newError(mue, "invalid URL path '" + paramValue + "'");
498         }
499
500         if (LOGGER.isDebugEnabled()) {
501             String JavaDoc msg = "XmlaServlet.makeDataSources: " +
502                     "dataSourcesConfigUrl="+dataSourcesConfigUrl;
503             LOGGER.debug(msg);
504         }
505         // don't try to parse a null
506
return (dataSourcesConfigUrl == null)
507             ? null : parseDataSourcesUrl(dataSourcesConfigUrl);
508     }
509
510     protected void addToDataSources(DataSourcesConfig.DataSources dataSources) {
511         if (this.dataSources == null) {
512             this.dataSources = dataSources;
513         } else if (dataSources != null) {
514             DataSourcesConfig.DataSource[] ds1 = this.dataSources.dataSources;
515             int len1 = ds1.length;
516             DataSourcesConfig.DataSource[] ds2 = dataSources.dataSources;
517             int len2 = ds2.length;
518
519             DataSourcesConfig.DataSource[] tmp =
520                 new DataSourcesConfig.DataSource[len1+len2];
521
522             System.arraycopy(ds1, 0, tmp, 0, len1);
523             System.arraycopy(ds2, 0, tmp, len1, len2);
524
525             this.dataSources.dataSources = tmp;
526         } else {
527             LOGGER.warn("XmlaServlet.addToDataSources: DataSources is null");
528         }
529     }
530
531     protected DataSourcesConfig.DataSources parseDataSourcesUrl(
532                 URL JavaDoc dataSourcesConfigUrl) {
533
534         try {
535             String JavaDoc dataSourcesConfigString =
536                 Util.readURL(
537                     dataSourcesConfigUrl,
538                     Util.toMap(System.getProperties()));
539             return parseDataSources(dataSourcesConfigString);
540
541         } catch (Exception JavaDoc e) {
542             throw Util.newError(e, "Failed to parse data sources config '" +
543                                 dataSourcesConfigUrl.toExternalForm() + "'");
544         }
545     }
546     protected DataSourcesConfig.DataSources parseDataSources(
547                 String JavaDoc dataSourcesConfigString) {
548
549         try {
550             if (dataSourcesConfigString == null) {
551                 LOGGER.warn("XmlaServlet.parseDataSources: null input");
552                 return null;
553             }
554             dataSourcesConfigString =
555                 Util.replaceProperties(
556                     dataSourcesConfigString,
557                     Util.toMap(System.getProperties()));
558
559         if (LOGGER.isDebugEnabled()) {
560             String JavaDoc msg = "XmlaServlet.parseDataSources: " +
561                     "dataSources="+dataSourcesConfigString;
562             LOGGER.debug(msg);
563         }
564             final Parser parser = XOMUtil.createDefaultParser();
565             final DOMWrapper doc = parser.parse(dataSourcesConfigString);
566             return new DataSourcesConfig.DataSources(doc);
567
568         } catch (XOMException e) {
569             throw Util.newError(e, "Failed to parse data sources config: " +
570                                 dataSourcesConfigString);
571         }
572     }
573
574     /**
575      * Initialize character encoding
576      */

577     protected void initCharEncodingHandler(ServletConfig JavaDoc servletConfig) {
578         String JavaDoc paramValue = servletConfig.getInitParameter(PARAM_CHAR_ENCODING);
579         if (paramValue != null) {
580             this.charEncoding = paramValue;
581         } else {
582             this.charEncoding = null;
583             LOGGER.warn("Use default character encoding from HTTP client");
584         }
585     }
586
587     /**
588      * Registers callbacks configured in web.xml.
589      */

590     protected void initCallbacks(ServletConfig JavaDoc servletConfig) {
591         String JavaDoc callbacksValue = servletConfig.getInitParameter(PARAM_CALLBACKS);
592
593         if (callbacksValue != null) {
594             String JavaDoc[] classNames = callbacksValue.split(";");
595
596             int count = 0;
597             nextCallback:
598             for (String JavaDoc className1 : classNames) {
599                 String JavaDoc className = className1.trim();
600
601                 try {
602                     Class JavaDoc<?> cls = Class.forName(className);
603                     if (XmlaRequestCallback.class.isAssignableFrom(cls)) {
604                         XmlaRequestCallback callback =
605                             (XmlaRequestCallback) cls.newInstance();
606
607                         try {
608                             callback.init(servletConfig);
609                         } catch (Exception JavaDoc e) {
610                             LOGGER.warn("Failed to initialize callback '" +
611                                 className + "'", e);
612                             continue nextCallback;
613                         }
614
615                         addCallback(callback);
616                         count++;
617
618                         if (LOGGER.isDebugEnabled()) {
619                             LOGGER.info("Register callback '" +
620                                 className + "'");
621                         }
622                     } else {
623                         LOGGER.warn("'" + className +
624                             "' is not an implementation of '" +
625                             XmlaRequestCallback.class + "'");
626                     }
627                 } catch (ClassNotFoundException JavaDoc cnfe) {
628                     LOGGER.warn("Callback class '" + className + "' not found",
629                         cnfe);
630                 } catch (InstantiationException JavaDoc ie) {
631                     LOGGER.warn("Can't instantiate class '" + className + "'",
632                         ie);
633                 } catch (IllegalAccessException JavaDoc iae) {
634                     LOGGER.warn("Can't instantiate class '" + className + "'",
635                         iae);
636                 }
637             }
638             LOGGER.debug("Registered " + count + " callback" + (count > 1 ? "s" : ""));
639         }
640     }
641
642 }
643
644 // End XmlaServlet.java
645
Popular Tags