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) 2005-2011 Pentaho
008// All Rights Reserved.
009*/
010package mondrian.xmla.impl;
011
012import mondrian.xmla.*;
013
014import org.olap4j.impl.Olap4jUtil;
015
016import org.w3c.dom.*;
017import org.xml.sax.InputSource;
018import org.xml.sax.SAXException;
019
020import java.io.*;
021import java.nio.ByteBuffer;
022import java.nio.channels.*;
023import java.util.HashMap;
024import java.util.Map;
025import javax.servlet.ServletConfig;
026import javax.servlet.ServletException;
027import javax.servlet.http.HttpServletRequest;
028import javax.servlet.http.HttpServletResponse;
029import javax.xml.parsers.*;
030
031/**
032 * Default implementation of XML/A servlet.
033 *
034 * @author Gang Chen
035 */
036public abstract class DefaultXmlaServlet extends XmlaServlet {
037
038    protected static final String nl = System.getProperty("line.separator");
039
040    /**
041     * Servlet config parameter that determines whether the xmla servlet
042     * requires authenticated sessions.
043     */
044    private static final String REQUIRE_AUTHENTICATED_SESSIONS =
045        "requireAuthenticatedSessions";
046
047    private DocumentBuilderFactory domFactory = null;
048
049    private boolean requireAuthenticatedSessions = false;
050
051    /**
052     * Session properties, keyed by session ID. Currently just username and
053     * password.
054     */
055    private final Map<String, SessionInfo> sessionInfos =
056        new HashMap<String, SessionInfo>();
057
058    public void init(ServletConfig servletConfig) throws ServletException {
059        super.init(servletConfig);
060        this.domFactory = getDocumentBuilderFactory();
061        this.requireAuthenticatedSessions =
062            Boolean.parseBoolean(
063                servletConfig.getInitParameter(REQUIRE_AUTHENTICATED_SESSIONS));
064    }
065
066    protected static DocumentBuilderFactory getDocumentBuilderFactory() {
067        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
068        factory.setIgnoringComments(true);
069        factory.setIgnoringElementContentWhitespace(true);
070        factory.setNamespaceAware(true);
071        return factory;
072    }
073
074    protected void unmarshallSoapMessage(
075        HttpServletRequest request,
076        Element[] requestSoapParts)
077        throws XmlaException
078    {
079        try {
080            InputStream inputStream;
081            try {
082                inputStream = request.getInputStream();
083            } catch (IllegalStateException ex) {
084                throw new XmlaException(
085                    SERVER_FAULT_FC,
086                    USM_REQUEST_STATE_CODE,
087                    USM_REQUEST_STATE_FAULT_FS,
088                    ex);
089            } catch (IOException ex) {
090                // This is either Client or Server
091                throw new XmlaException(
092                    SERVER_FAULT_FC,
093                    USM_REQUEST_INPUT_CODE,
094                    USM_REQUEST_INPUT_FAULT_FS,
095                    ex);
096            }
097
098            DocumentBuilder domBuilder;
099            try {
100                domBuilder = domFactory.newDocumentBuilder();
101            } catch (ParserConfigurationException ex) {
102                throw new XmlaException(
103                    SERVER_FAULT_FC,
104                    USM_DOM_FACTORY_CODE,
105                    USM_DOM_FACTORY_FAULT_FS,
106                    ex);
107            }
108
109            Document soapDoc;
110            try {
111                soapDoc = domBuilder.parse(new InputSource(inputStream));
112            } catch (IOException ex) {
113                // This is either Client or Server
114                throw new XmlaException(
115                    SERVER_FAULT_FC,
116                    USM_DOM_PARSE_IO_CODE,
117                    USM_DOM_PARSE_IO_FAULT_FS,
118                    ex);
119            } catch (SAXException ex) {
120                // Assume client passed bad xml
121                throw new XmlaException(
122                    CLIENT_FAULT_FC,
123                    USM_DOM_PARSE_CODE,
124                    USM_DOM_PARSE_FAULT_FS,
125                    ex);
126            }
127
128            /* Check SOAP message */
129            Element envElem = soapDoc.getDocumentElement();
130
131            if (LOGGER.isDebugEnabled()) {
132                logXmlaRequest(envElem);
133            }
134
135            if ("Envelope".equals(envElem.getLocalName())) {
136                if (!(NS_SOAP_ENV_1_1.equals(envElem.getNamespaceURI()))) {
137                    throw new XmlaException(
138                        CLIENT_FAULT_FC,
139                        USM_DOM_PARSE_CODE,
140                        USM_DOM_PARSE_FAULT_FS,
141                        new SAXException(
142                            "Invalid SOAP message: "
143                            + "Envelope element not in SOAP namespace"));
144                }
145            } else {
146                throw new XmlaException(
147                    CLIENT_FAULT_FC,
148                    USM_DOM_PARSE_CODE,
149                    USM_DOM_PARSE_FAULT_FS,
150                    new SAXException(
151                        "Invalid SOAP message: "
152                        + "Top element not Envelope"));
153            }
154
155            Element[] childs =
156                XmlaUtil.filterChildElements(
157                    envElem, NS_SOAP_ENV_1_1, "Header");
158            if (childs.length > 1) {
159                throw new XmlaException(
160                    CLIENT_FAULT_FC,
161                    USM_DOM_PARSE_CODE,
162                    USM_DOM_PARSE_FAULT_FS,
163                    new SAXException(
164                        "Invalid SOAP message: "
165                        + "More than one Header elements"));
166            }
167            requestSoapParts[0] = childs.length == 1 ? childs[0] : null;
168
169            childs =
170                XmlaUtil.filterChildElements(envElem, NS_SOAP_ENV_1_1, "Body");
171            if (childs.length != 1) {
172                throw new XmlaException(
173                    CLIENT_FAULT_FC,
174                    USM_DOM_PARSE_CODE,
175                    USM_DOM_PARSE_FAULT_FS,
176                    new SAXException(
177                        "Invalid SOAP message: "
178                        + "Does not have one Body element"));
179            }
180            requestSoapParts[1] = childs[0];
181        } catch (XmlaException xex) {
182            throw xex;
183        } catch (Exception ex) {
184            throw new XmlaException(
185                SERVER_FAULT_FC,
186                USM_UNKNOWN_CODE,
187                USM_UNKNOWN_FAULT_FS,
188                ex);
189        }
190    }
191
192    protected void logXmlaRequest(Element envElem) {
193        final StringWriter writer = new StringWriter();
194        writer.write("XML/A request content");
195        writer.write(nl);
196        XmlaUtil.element2Text(envElem, writer);
197        LOGGER.debug(writer.toString());
198    }
199
200    /**
201     * {@inheritDoc}
202     *
203     * <p>See if there is a "mustUnderstand" header element.
204     * If there is a BeginSession element, then generate a session id and
205     * add to context Map.</p>
206     *
207     * <p>Excel 2000 and Excel XP generate both a BeginSession, Session and
208     * EndSession mustUnderstand=1
209     * in the "urn:schemas-microsoft-com:xml-analysis" namespace
210     * Header elements and a NamespaceCompatibility mustUnderstand=0
211     * in the "http://schemas.microsoft.com/analysisservices/2003/xmla"
212     * namespace. Here we handle only the session Header elements.
213     *
214     * <p>We also handle the Security element.</p>
215     */
216    protected void handleSoapHeader(
217        HttpServletResponse response,
218        Element[] requestSoapParts,
219        byte[][] responseSoapParts,
220        Map<String, Object> context) throws XmlaException
221    {
222        try {
223            Element hdrElem = requestSoapParts[0];
224            if ((hdrElem == null) || (! hdrElem.hasChildNodes())) {
225                return;
226            }
227
228            String encoding = response.getCharacterEncoding();
229
230            byte[] bytes = null;
231
232            NodeList nlst = hdrElem.getChildNodes();
233            int nlen = nlst.getLength();
234            boolean authenticatedSession = false;
235            boolean beginSession = false;
236            for (int i = 0; i < nlen; i++) {
237                Node n = nlst.item(i);
238                if (!(n instanceof Element)) {
239                    continue;
240                }
241                Element e = (Element) n;
242                String localName = e.getLocalName();
243
244                if (localName.equals(XMLA_SECURITY)
245                    && NS_SOAP_SECEXT.equals(e.getNamespaceURI()))
246                {
247                    // Example:
248                    //
249                    // <Security xmlns="http://schemas.xmlsoap.org/ws/2002/04/secext">
250                    //   <UsernameToken>
251                    //     <Username>MICHELE</Username>
252                    //     <Password Type="PasswordText">ROSSI</Password>
253                    //   </UsernameToken>
254                    // </Security>
255                    // <BeginSession mustUnderstand="1"
256                    //   xmlns="urn:schemas-microsoft-com:xml-analysis" />
257                    NodeList childNodes = e.getChildNodes();
258                    Element userNameToken = (Element) childNodes.item(1);
259                    NodeList userNamePassword = userNameToken.getChildNodes();
260                    Element username = (Element) userNamePassword.item(1);
261                    Element password = (Element) userNamePassword.item(3);
262                    String userNameStr =
263                        username.getChildNodes().item(0).getNodeValue();
264                    context.put(CONTEXT_XMLA_USERNAME, userNameStr);
265                    String passwordStr = "";
266
267                    if (password.getChildNodes().item(0) != null) {
268                        passwordStr =
269                            password.getChildNodes().item(0).getNodeValue();
270                    }
271
272                    context.put(CONTEXT_XMLA_PASSWORD, passwordStr);
273
274                    if ("".equals(passwordStr) || null == passwordStr) {
275                        LOGGER.warn(
276                            "Security header for user [" + userNameStr
277                            + "] provided without password");
278                    }
279                    authenticatedSession = true;
280                    continue;
281                }
282
283                // Make sure Element has mustUnderstand=1 attribute.
284                Attr attr = e.getAttributeNode(SOAP_MUST_UNDERSTAND_ATTR);
285                boolean mustUnderstandValue =
286                    attr != null
287                    && attr.getValue() != null
288                    && attr.getValue().equals("1");
289
290                if (!mustUnderstandValue) {
291                    continue;
292                }
293
294                // Is it an XMLA element
295                if (!NS_XMLA.equals(e.getNamespaceURI())) {
296                    continue;
297                }
298                // So, an XMLA mustUnderstand-er
299                // Do we know what to do with it
300                // We understand:
301                //    BeginSession
302                //    Session
303                //    EndSession
304
305                String sessionIdStr;
306                if (localName.equals(XMLA_BEGIN_SESSION)) {
307                    sessionIdStr = generateSessionId(context);
308
309                    context.put(CONTEXT_XMLA_SESSION_ID, sessionIdStr);
310                    context.put(
311                        CONTEXT_XMLA_SESSION_STATE,
312                        CONTEXT_XMLA_SESSION_STATE_BEGIN);
313
314                } else if (localName.equals(XMLA_SESSION)) {
315                    sessionIdStr = getSessionIdFromRequest(e, context);
316
317                    SessionInfo sessionInfo = getSessionInfo(sessionIdStr);
318
319                    if (sessionInfo != null) {
320                        context.put(CONTEXT_XMLA_USERNAME, sessionInfo.user);
321                        context.put(
322                            CONTEXT_XMLA_PASSWORD,
323                            sessionInfo.password);
324                    }
325
326                    context.put(CONTEXT_XMLA_SESSION_ID, sessionIdStr);
327                    context.put(
328                        CONTEXT_XMLA_SESSION_STATE,
329                        CONTEXT_XMLA_SESSION_STATE_WITHIN);
330
331                } else if (localName.equals(XMLA_END_SESSION)) {
332                    sessionIdStr = getSessionIdFromRequest(e, context);
333                    context.put(
334                        CONTEXT_XMLA_SESSION_STATE,
335                        CONTEXT_XMLA_SESSION_STATE_END);
336
337                } else {
338                    // error
339                    String msg =
340                        "Invalid XML/A message: Unknown "
341                        + "\"mustUnderstand\" XMLA Header element \""
342                        + localName
343                        + "\"";
344                    throw new XmlaException(
345                        MUST_UNDERSTAND_FAULT_FC,
346                        HSH_MUST_UNDERSTAND_CODE,
347                        HSH_MUST_UNDERSTAND_FAULT_FS,
348                        new RuntimeException(msg));
349                }
350
351                StringBuilder buf = new StringBuilder(100);
352                buf.append("<Session ");
353                buf.append(XMLA_SESSION_ID);
354                buf.append("=\"");
355                buf.append(sessionIdStr);
356                buf.append("\" ");
357                buf.append("xmlns=\"");
358                buf.append(NS_XMLA);
359                buf.append("\" />");
360                bytes = buf.toString().getBytes(encoding);
361
362                if (authenticatedSession) {
363                    String username =
364                        (String) context.get(CONTEXT_XMLA_USERNAME);
365                    String password =
366                        (String) context.get(CONTEXT_XMLA_PASSWORD);
367                    String sessionId =
368                        (String) context.get(CONTEXT_XMLA_SESSION_ID);
369
370                    LOGGER.debug(
371                        "New authenticated session; storing credentials ["
372                        + username + "/********] for session id ["
373                        + sessionId + "]");
374
375                    saveSessionInfo(
376                        username,
377                        password,
378                        sessionId);
379                } else {
380                    if (beginSession && requireAuthenticatedSessions) {
381                        throw new XmlaException(
382                            XmlaConstants.CLIENT_FAULT_FC,
383                            XmlaConstants.CHH_AUTHORIZATION_CODE,
384                            XmlaConstants.CHH_AUTHORIZATION_FAULT_FS,
385                            new Exception("Session Credentials NOT PROVIDED"));
386                    }
387                }
388            }
389            responseSoapParts[0] = bytes;
390        } catch (XmlaException xex) {
391            throw xex;
392        } catch (Exception ex) {
393            throw new XmlaException(
394                SERVER_FAULT_FC,
395                HSH_UNKNOWN_CODE,
396                HSH_UNKNOWN_FAULT_FS,
397                ex);
398        }
399    }
400
401
402    protected String generateSessionId(Map<String, Object> context) {
403        for (XmlaRequestCallback callback : getCallbacks()) {
404            final String sessionId = callback.generateSessionId(context);
405            if (sessionId != null) {
406                return sessionId;
407            }
408        }
409
410
411        // Generate a pseudo-random new session ID.
412        return Long.toString(
413            17L * System.nanoTime()
414            + 3L * System.currentTimeMillis(), 35);
415    }
416
417
418    private static String getSessionIdFromRequest(
419        Element e,
420        Map<String, Object> context)
421        throws Exception
422    {
423        // extract the SessionId attrs value and put into context
424        Attr attr = e.getAttributeNode(XMLA_SESSION_ID);
425        if (attr == null) {
426            throw new SAXException(
427                "Invalid XML/A message: "
428                + XMLA_SESSION
429                + " Header element with no "
430                + XMLA_SESSION_ID
431                + " attribute");
432        }
433
434        String sessionId = attr.getValue();
435        if (sessionId == null) {
436            throw new SAXException(
437                "Invalid XML/A message: "
438                + XMLA_SESSION
439                + " Header element with "
440                + XMLA_SESSION_ID
441                + " attribute but no attribute value");
442        }
443        return sessionId;
444    }
445
446    protected void handleSoapBody(
447        HttpServletResponse response,
448        Element[] requestSoapParts,
449        byte[][] responseSoapParts,
450        Map<String, Object> context)
451        throws XmlaException
452    {
453        try {
454            String encoding = response.getCharacterEncoding();
455            Element hdrElem = requestSoapParts[0]; // not used
456            Element bodyElem = requestSoapParts[1];
457            Element[] dreqs =
458                XmlaUtil.filterChildElements(bodyElem, NS_XMLA, "Discover");
459            Element[] ereqs =
460                XmlaUtil.filterChildElements(bodyElem, NS_XMLA, "Execute");
461            if (dreqs.length + ereqs.length != 1) {
462                throw new XmlaException(
463                    CLIENT_FAULT_FC,
464                    HSB_BAD_SOAP_BODY_CODE,
465                    HSB_BAD_SOAP_BODY_FAULT_FS,
466                    new RuntimeException(
467                        "Invalid XML/A message: Body has "
468                        + dreqs.length + " Discover Requests and "
469                        + ereqs.length + " Execute Requests"));
470            }
471
472            Element xmlaReqElem = (dreqs.length == 0 ? ereqs[0] : dreqs[0]);
473
474            ByteArrayOutputStream osBuf = new ByteArrayOutputStream();
475
476            // use context variable 'role_name' as this request's XML/A role
477            String roleName = (String) context.get(CONTEXT_ROLE_NAME);
478
479            String username = (String) context.get(CONTEXT_XMLA_USERNAME);
480            String password = (String) context.get(CONTEXT_XMLA_PASSWORD);
481            String sessionId = (String) context.get(CONTEXT_XMLA_SESSION_ID);
482            XmlaRequest xmlaReq =
483                new DefaultXmlaRequest(
484                    xmlaReqElem, roleName, username, password, sessionId);
485
486            // "ResponseMimeType" may be in the context if the "Accept" HTTP
487            // header was specified. But override if the SOAP request has the
488            // "ResponseMimeType" property.
489            Enumeration.ResponseMimeType responseMimeType =
490                Enumeration.ResponseMimeType.SOAP;
491            final String responseMimeTypeName =
492                xmlaReq.getProperties().get("ResponseMimeType");
493            if (responseMimeTypeName != null) {
494                responseMimeType =
495                    Enumeration.ResponseMimeType.MAP.get(
496                        responseMimeTypeName);
497                if (responseMimeType != null) {
498                    context.put(CONTEXT_MIME_TYPE, responseMimeType);
499                }
500            }
501
502            XmlaResponse xmlaRes =
503                new DefaultXmlaResponse(osBuf, encoding, responseMimeType);
504
505            try {
506                getXmlaHandler().process(xmlaReq, xmlaRes);
507            } catch (XmlaException ex) {
508                throw ex;
509            } catch (Exception ex) {
510                throw new XmlaException(
511                    SERVER_FAULT_FC,
512                    HSB_PROCESS_CODE,
513                    HSB_PROCESS_FAULT_FS,
514                    ex);
515            }
516
517            responseSoapParts[1] = osBuf.toByteArray();
518        } catch (XmlaException xex) {
519            throw xex;
520        } catch (Exception ex) {
521            throw new XmlaException(
522                SERVER_FAULT_FC,
523                HSB_UNKNOWN_CODE,
524                HSB_UNKNOWN_FAULT_FS,
525                ex);
526        }
527    }
528
529    protected void marshallSoapMessage(
530        HttpServletResponse response,
531        byte[][] responseSoapParts,
532        Enumeration.ResponseMimeType responseMimeType)
533        throws XmlaException
534    {
535        try {
536            // If CharacterEncoding was set in web.xml, use this value
537            String encoding =
538                (charEncoding != null)
539                    ? charEncoding
540                    : response.getCharacterEncoding();
541
542            /*
543             * Since we just reset response, encoding and content-type were
544             * reset too
545             */
546            if (charEncoding != null) {
547                response.setCharacterEncoding(charEncoding);
548            }
549            switch (responseMimeType) {
550            case JSON:
551                response.setContentType("application/json");
552                break;
553            case SOAP:
554            default:
555                response.setContentType("text/xml");
556                break;
557            }
558
559            /*
560             * The setCharacterEncoding, setContentType, or setLocale method
561             * must be called BEFORE getWriter or getOutputStream and before
562             * committing the response for the character encoding to be used.
563             *
564             * @see javax.servlet.ServletResponse
565             */
566            OutputStream outputStream = response.getOutputStream();
567
568
569            byte[] soapHeader = responseSoapParts[0];
570            byte[] soapBody = responseSoapParts[1];
571
572            Object[] byteChunks = null;
573
574            try {
575                switch (responseMimeType) {
576                case JSON:
577                    byteChunks = new Object[] {
578                        soapBody,
579                    };
580                    break;
581
582                case SOAP:
583                default:
584                    String s0 =
585                        "<?xml version=\"1.0\" encoding=\"" + encoding
586                        + "\"?>\n<" + SOAP_PREFIX + ":Envelope xmlns:"
587                        + SOAP_PREFIX + "=\"" + NS_SOAP_ENV_1_1 + "\" "
588                        + SOAP_PREFIX + ":encodingStyle=\""
589                        + NS_SOAP_ENC_1_1 + "\" >" + "\n<" + SOAP_PREFIX
590                        + ":Header>\n";
591                    String s2 =
592                        "</" + SOAP_PREFIX + ":Header>\n<" + SOAP_PREFIX
593                        + ":Body>\n";
594                    String s4 =
595                        "\n</" + SOAP_PREFIX + ":Body>\n</" + SOAP_PREFIX
596                        + ":Envelope>\n";
597
598                    byteChunks = new Object[] {
599                        s0.getBytes(encoding),
600                        soapHeader,
601                        s2.getBytes(encoding),
602                        soapBody,
603                        s4.getBytes(encoding),
604                    };
605                    break;
606                }
607            } catch (UnsupportedEncodingException uee) {
608                LOGGER.warn(
609                    "This should be handled at begin of processing request",
610                    uee);
611            }
612
613            if (LOGGER.isDebugEnabled()) {
614                StringBuilder buf = new StringBuilder(100);
615                buf.append("XML/A response content").append(nl);
616                try {
617                    for (Object byteChunk : byteChunks) {
618                        byte[] chunk = (byte[]) byteChunk;
619                        if (chunk != null && chunk.length > 0) {
620                            buf.append(new String(chunk, encoding));
621                        }
622                    }
623                } catch (UnsupportedEncodingException uee) {
624                    LOGGER.warn(
625                        "This should be handled at begin of processing request",
626                        uee);
627                }
628                LOGGER.debug(buf.toString());
629            }
630
631            if (LOGGER.isDebugEnabled()) {
632                StringBuilder buf = new StringBuilder();
633                buf.append("XML/A response content").append(nl);
634            }
635            try {
636                int bufferSize = 4096;
637                ByteBuffer buffer = ByteBuffer.allocate(bufferSize);
638                WritableByteChannel wch = Channels.newChannel(outputStream);
639                ReadableByteChannel rch;
640                for (Object byteChunk : byteChunks) {
641                    if (byteChunk == null || ((byte[]) byteChunk).length == 0) {
642                        continue;
643                    }
644                    rch = Channels.newChannel(
645                        new ByteArrayInputStream((byte[]) byteChunk));
646
647                    int readSize;
648                    do {
649                        buffer.clear();
650                        readSize = rch.read(buffer);
651                        buffer.flip();
652
653                        int writeSize = 0;
654                        while ((writeSize += wch.write(buffer)) < readSize) {
655                        }
656                    } while (readSize == bufferSize);
657                    rch.close();
658                }
659                outputStream.flush();
660            } catch (IOException ioe) {
661                LOGGER.error(
662                    "Damn exception when transferring bytes over sockets",
663                    ioe);
664            }
665        } catch (XmlaException xex) {
666            throw xex;
667        } catch (Exception ex) {
668            throw new XmlaException(
669                SERVER_FAULT_FC,
670                MSM_UNKNOWN_CODE,
671                MSM_UNKNOWN_FAULT_FS,
672                ex);
673        }
674    }
675
676    /**
677     * This produces a SOAP 1.1 version Fault element - not a 1.2 version.
678     *
679     */
680    protected void handleFault(
681        HttpServletResponse response,
682        byte[][] responseSoapParts,
683        Phase phase,
684        Throwable t)
685    {
686        // Regardless of whats been put into the response so far, clear
687        // it out.
688        response.reset();
689
690        // NOTE: if you can think of better/other status codes to use
691        // for the various phases, please make changes.
692        // I think that XMLA faults always returns OK.
693        switch (phase) {
694        case VALIDATE_HTTP_HEAD:
695            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
696            break;
697        case INITIAL_PARSE:
698        case CALLBACK_PRE_ACTION:
699        case PROCESS_HEADER:
700        case PROCESS_BODY:
701        case CALLBACK_POST_ACTION:
702        case SEND_RESPONSE:
703            response.setStatus(HttpServletResponse.SC_OK);
704            break;
705        }
706
707        String code;
708        String faultCode;
709        String faultString;
710        String detail;
711        if (t instanceof XmlaException) {
712            XmlaException xex = (XmlaException) t;
713            code = xex.getCode();
714            faultString = xex.getFaultString();
715            faultCode = XmlaException.formatFaultCode(xex);
716            detail = XmlaException.formatDetail(xex.getDetail());
717
718        } else {
719            // some unexpected Throwable
720            t = XmlaException.getRootCause(t);
721            code = UNKNOWN_ERROR_CODE;
722            faultString = UNKNOWN_ERROR_FAULT_FS;
723            faultCode = XmlaException.formatFaultCode(
724                SERVER_FAULT_FC, code);
725            detail = XmlaException.formatDetail(t.getMessage());
726        }
727
728        String encoding = response.getCharacterEncoding();
729
730        ByteArrayOutputStream osBuf = new ByteArrayOutputStream();
731        try {
732            SaxWriter writer = new DefaultSaxWriter(osBuf, encoding);
733            writer.startDocument();
734            writer.startElement(SOAP_PREFIX + ":Fault");
735
736            // The faultcode element is intended for use by software to provide
737            // an algorithmic mechanism for identifying the fault. The faultcode
738            // MUST be present in a SOAP Fault element and the faultcode value
739            // MUST be a qualified name
740            writer.startElement("faultcode");
741            writer.characters(faultCode);
742            writer.endElement();
743
744            // The faultstring element is intended to provide a human readable
745            // explanation of the fault and is not intended for algorithmic
746            // processing.
747            writer.startElement("faultstring");
748            writer.characters(faultString);
749            writer.endElement();
750
751            // The faultactor element is intended to provide information about
752            // who caused the fault to happen within the message path
753            writer.startElement("faultactor");
754            writer.characters(FAULT_ACTOR);
755            writer.endElement();
756
757            // The detail element is intended for carrying application specific
758            // error information related to the Body element. It MUST be present
759            // if the contents of the Body element could not be successfully
760            // processed. It MUST NOT be used to carry information about error
761            // information belonging to header entries. Detailed error
762            // information belonging to header entries MUST be carried within
763            // header entries.
764            if (phase != Phase.PROCESS_HEADER) {
765                writer.startElement("detail");
766                writer.startElement(
767                    FAULT_NS_PREFIX + ":error",
768                    "xmlns:" + FAULT_NS_PREFIX, MONDRIAN_NAMESPACE);
769                writer.startElement("code");
770                writer.characters(code);
771                writer.endElement(); // code
772                writer.startElement("desc");
773                writer.characters(detail);
774                writer.endElement(); // desc
775                writer.endElement(); // error
776                writer.endElement(); // detail
777            }
778
779            writer.endElement();   // </Fault>
780            writer.endDocument();
781        } catch (UnsupportedEncodingException uee) {
782            LOGGER.warn(
783                "This should be handled at begin of processing request",
784                uee);
785        } catch (Exception e) {
786            LOGGER.error(
787                "Unexcepted runimt exception when handing SOAP fault :(");
788        }
789
790        responseSoapParts[1] = osBuf.toByteArray();
791    }
792
793    private SessionInfo getSessionInfo(String sessionId) {
794        if (sessionId == null) {
795            return null;
796        }
797
798        SessionInfo sessionInfo = null;
799
800        synchronized (sessionInfos) {
801            sessionInfo = sessionInfos.get(sessionId);
802        }
803
804        if (sessionInfo == null) {
805            LOGGER.error(
806                "No login credentials for found for session ["+
807                sessionId + "]");
808        } else {
809            LOGGER.debug(
810                "Found credentials for session id ["
811                + sessionId
812                + "], username=[" + sessionInfo.user
813                + "] in servlet cache");
814        }
815        return sessionInfo;
816    }
817
818    private SessionInfo saveSessionInfo(
819        String username,
820        String password,
821        String sessionId)
822    {
823        synchronized (sessionInfos) {
824            SessionInfo sessionInfo = sessionInfos.get(sessionId);
825            if (sessionInfo != null
826                && Olap4jUtil.equal(sessionInfo.user, username))
827            {
828                // Overwrite the password, but only if it is non-empty.
829                // (Sometimes Simba sends the credentials object again
830                // but without a password.)
831                if (password != null && password.length() > 0) {
832                    sessionInfo =
833                        new SessionInfo(username, password);
834                    sessionInfos.put(sessionId, sessionInfo);
835                }
836            } else {
837                // A credentials object was stored against the provided session
838                // ID but the username didn't match, so create a new holder.
839                sessionInfo = new SessionInfo(username, password);
840                sessionInfos.put(sessionId, sessionInfo);
841            }
842            return sessionInfo;
843        }
844    }
845    /**
846     * Holds authentication credentials of a XMLA session.
847     */
848    private static class SessionInfo {
849        final String user;
850        final String password;
851
852        public SessionInfo(String user, String password)
853        {
854            this.user = user;
855            this.password = password;
856        }
857    }
858}
859// End DefaultXmlaServlet.java