001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.servicemix.soap.interceptors.jbi;
018    
019    import java.util.List;
020    import java.util.ArrayList;
021    import java.util.Collections;
022    
023    import javax.xml.XMLConstants;
024    import javax.xml.namespace.QName;
025    import javax.xml.namespace.NamespaceContext;
026    import javax.xml.stream.XMLStreamReader;
027    import javax.xml.stream.XMLStreamException;
028    import javax.xml.stream.Location;
029    import javax.xml.transform.Source;
030    import javax.xml.transform.dom.DOMSource;
031    
032    import org.apache.servicemix.soap.api.Fault;
033    import org.apache.servicemix.soap.api.Message;
034    import org.apache.servicemix.soap.api.model.Operation;
035    import org.apache.servicemix.soap.bindings.soap.SoapFault;
036    import org.apache.servicemix.soap.bindings.soap.model.wsdl1.Wsdl1SoapBinding;
037    import org.apache.servicemix.soap.bindings.soap.model.wsdl1.Wsdl1SoapMessage;
038    import org.apache.servicemix.soap.bindings.soap.model.wsdl1.Wsdl1SoapOperation;
039    import org.apache.servicemix.soap.bindings.soap.model.wsdl1.Wsdl1SoapPart;
040    import org.apache.servicemix.soap.core.AbstractInterceptor;
041    import org.apache.servicemix.soap.util.DomUtil;
042    import org.apache.servicemix.soap.util.QNameUtil;
043    import org.apache.servicemix.soap.util.stax.StaxUtil;
044    import org.apache.servicemix.soap.util.stax.StaxSource;
045    import org.apache.servicemix.soap.util.stax.DOMStreamReader;
046    import org.apache.servicemix.soap.util.stax.FragmentStreamReader;
047    import org.apache.servicemix.soap.util.stax.ExtendedXMLStreamReader;
048    
049    import org.w3c.dom.Document;
050    import org.w3c.dom.DocumentFragment;
051    import org.w3c.dom.Element;
052    import org.w3c.dom.Node;
053    import org.w3c.dom.NodeList;
054    
055    /**
056     * @author <a href="mailto:gnodet [at] gmail.com">Guillaume Nodet</a>
057     */
058    public class JbiInWsdl1Interceptor extends AbstractInterceptor {
059    
060        private final boolean server;
061        
062        public JbiInWsdl1Interceptor(boolean server) {
063            this.server = server;
064        }
065        
066        public void handleMessage(Message message) {
067            // Ignore faults messages
068            if (message.getContent(Exception.class) != null) {
069                return;
070            }
071            // Check if we should not use the JBI wrapper
072            if (message.get(JbiConstants.USE_JBI_WRAPPER) instanceof Boolean && ((Boolean) message.get(JbiConstants.USE_JBI_WRAPPER)) == false) {
073                XMLStreamReader xmlReader = message.getContent(XMLStreamReader.class);
074                if (xmlReader != null) {
075                    message.setContent(Source.class, StaxUtil.createSource(xmlReader));
076                }
077                return;
078            }
079    
080            try {
081                XMLStreamReader reader = new StaxJbiWrapper(message);
082                message.setContent(Source.class, new StaxSource(reader));
083                //throw new UnsupportedOperationException();
084            } catch (UnsupportedOperationException e) {
085                Document document = createDomWrapper(message);
086                message.setContent(Source.class, new DOMSource(document));
087            }
088        }
089    
090        protected Document createDomWrapper(Message message) {
091            Wsdl1SoapOperation wsdlOperation = getOperation(message);
092            Wsdl1SoapMessage wsdlMessage = server ? wsdlOperation.getInput() : wsdlOperation.getOutput();
093            Document document = DomUtil.createDocument();
094            Element root = DomUtil.createElement(document, JbiConstants.WSDL11_WRAPPER_MESSAGE);
095            String typeNamespace = wsdlMessage.getName().getNamespaceURI();
096            if (typeNamespace == null || typeNamespace.length() == 0) {
097                throw new IllegalArgumentException("messageType namespace is null or empty");
098            }
099            root.setAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":" + JbiConstants.WSDL11_WRAPPER_MESSAGE_PREFIX,
100                              typeNamespace);
101            String typeLocalName = wsdlMessage.getName().getLocalPart();
102            if (typeLocalName == null || typeLocalName.length() == 0) {
103                throw new IllegalArgumentException("messageType local name is null or empty");
104            }
105            root.setAttribute(JbiConstants.WSDL11_WRAPPER_TYPE, JbiConstants.WSDL11_WRAPPER_MESSAGE_PREFIX + ":" + typeLocalName);
106            String messageName = wsdlMessage.getMessageName();
107            root.setAttribute(JbiConstants.WSDL11_WRAPPER_NAME, messageName);
108            root.setAttribute(JbiConstants.WSDL11_WRAPPER_VERSION, "1.0");
109    
110            Element body = getBodyElement(message);
111            for (Wsdl1SoapPart part : wsdlMessage.getParts()) {
112                if (part.isBody()) {
113                    if (wsdlOperation.getStyle() == Wsdl1SoapBinding.Style.DOCUMENT) {
114                        addPart(root, body);
115                    } else /* rpc-style */ {
116                        // SOAP:Body element is the operation name, children are operation parameters
117                        Element param = DomUtil.getFirstChildElement(body);
118                        boolean found = false;
119                        while (param != null) {
120                            if (part.getName().equals(param.getLocalName())) {
121                                found = true;
122                                addPart(root, param.getChildNodes());
123                            }
124                            param = DomUtil.getNextSiblingElement(param);
125                        }
126                        if (!found) {
127                            throw new SoapFault(SoapFault.SENDER, "Missing part '" + part.getName() + "'");
128                        }
129                    }
130                } else {
131                    QName element = part.getElement();
132                    DocumentFragment header = message.getSoapHeaders().remove(element);
133                    if (header == null) {
134                        throw new SoapFault(SoapFault.SENDER, "Missing required header element: "
135                                    + QNameUtil.toString(element));
136                    }
137                    addPart(root, header.getChildNodes());
138                }
139            }
140            return document;
141        }
142    
143        protected Wsdl1SoapOperation getOperation(Message message) {
144            Operation operation = message.get(Operation.class);
145            if (operation == null) {
146                throw new Fault("Operation not bound on this message");
147            }
148            if (operation instanceof Wsdl1SoapOperation == false) {
149                throw new Fault("Message is not bound to a WSDL 1.1 SOAP operation");
150            }
151            return (Wsdl1SoapOperation) operation;
152        }
153    
154        /**
155         * Extract the content as a jaxp Source
156         */
157        protected Element getBodyElement(Message message) {
158            XMLStreamReader xmlReader = message.getContent(XMLStreamReader.class);
159            return xmlReader != null ? StaxUtil.createElement(xmlReader) : null; 
160        }
161        
162        /**
163         * Add a jbi:part to a normalized message document
164         */
165        private static void addPart(Element parent, Node partValue) {
166            Element element = DomUtil.createElement(parent, JbiConstants.WSDL11_WRAPPER_PART);
167            element.appendChild(element.getOwnerDocument().importNode(partValue, true));
168        }
169    
170        /**
171         * Add a jbi:part to a normalized message document
172         */
173        private static void addPart(Element parent, NodeList nodes) {
174            Element element = DomUtil.createElement(parent, JbiConstants.WSDL11_WRAPPER_PART);
175            for (int i = 0; i < nodes.getLength(); i++) {
176                Node node = nodes.item(i);
177                element.appendChild(element.getOwnerDocument().importNode(node, true));
178            }
179        }
180    
181        protected class StaxJbiWrapper implements XMLStreamReader {
182            public static final int STATE_START_DOC = 0;
183            public static final int STATE_START_ELEMENT_WRAPPER = 1;
184            public static final int STATE_START_ELEMENT_PART = 2;
185            public static final int STATE_RUN_PART = 3;
186            public static final int STATE_END_ELEMENT_PART = 4;
187            public static final int STATE_END_ELEMENT_WRAPPER = 5;
188            public static final int STATE_END_DOC = 6;
189    
190            private Message message;
191            private Wsdl1SoapMessage wsdlMessage;
192            private int state = STATE_START_DOC;
193            private int part = -1;
194            private int reader = -1;
195            private int event = START_DOCUMENT;
196            private List<List<XMLStreamReader>> parts = new ArrayList<List<XMLStreamReader>>();
197    
198            public StaxJbiWrapper(Message message) {
199                this.message = message;
200                Wsdl1SoapOperation wsdlOperation = getOperation(message);
201                wsdlMessage = server ? wsdlOperation.getInput() : wsdlOperation.getOutput();
202                XMLStreamReader xmlReader = message.getContent(XMLStreamReader.class);
203                int nbBodyParts = 0;
204                for (Wsdl1SoapPart part : wsdlMessage.getParts()) {
205                    if (part.isBody()) {
206                        if (nbBodyParts++ > 0) {
207                            throw new UnsupportedOperationException();
208                        }
209                        if (wsdlOperation.getStyle() == Wsdl1SoapBinding.Style.DOCUMENT) {
210                            parts.add(Collections.<XMLStreamReader>singletonList(new FragmentStreamReader(xmlReader)));
211                        } else /* rpc-style */ {
212                            throw new UnsupportedOperationException();
213                        }
214                    } else {
215                        QName element = part.getElement();
216                        DocumentFragment header = message.getSoapHeaders().get(element);
217                        if (header == null) {
218                            throw new SoapFault(SoapFault.SENDER, "Missing required header element: "
219                                        + QNameUtil.toString(element));
220                        }
221                        List<XMLStreamReader> readers = new ArrayList<XMLStreamReader>();
222                        NodeList l = header.getChildNodes();
223                        for (int i = 0; i < l.getLength(); i++) {
224                            Node n = l.item(i);
225                            if (n instanceof Element) {
226                                readers.add(new DOMStreamReader((Element) n));
227                            }
228                        }
229                        parts.add(readers);
230                    }
231                }
232                // Now that we know we can handle the message, remove header parts that are included in the jbi wrapper
233                for (Wsdl1SoapPart part : wsdlMessage.getParts()) {
234                    if (!part.isBody()) {
235                        message.getSoapHeaders().remove(part.getElement());
236                    }
237                }
238            }
239    
240            public int getEventType() {
241                return event;
242            }
243    
244            public int next() throws XMLStreamException {
245                switch (state) {
246                    case STATE_START_DOC:
247                        state = STATE_START_ELEMENT_WRAPPER;
248                        event = START_ELEMENT;
249                        break;
250                    case STATE_START_ELEMENT_WRAPPER:
251                        if (parts.size() > 0) {
252                            state = STATE_START_ELEMENT_PART;
253                            event = START_ELEMENT;
254                            part = 0;
255                            reader = 0;
256                        } else {
257                            state = STATE_END_ELEMENT_WRAPPER;
258                            event = END_ELEMENT;
259                        }
260                        break;
261                    case STATE_START_ELEMENT_PART:
262                        if (reader >= parts.get(part).size()) {
263                            state = STATE_END_ELEMENT_PART;
264                            event = END_ELEMENT;
265                        } else {
266                            state = STATE_RUN_PART;
267                            event = parts.get(part).get(reader).next();
268                            if (event == START_DOCUMENT) {
269                                event = parts.get(part).get(reader).next();
270                            }
271                        }
272                        break;
273                    case STATE_RUN_PART:
274                        event = parts.get(part).get(reader).next();
275                        if (event == END_DOCUMENT) {
276                            if (++reader >= parts.get(part).size()) {
277                                state = STATE_END_ELEMENT_PART;
278                                event = END_ELEMENT;
279                            } else {
280                                event = parts.get(part).get(reader).next();
281                                if (event == START_DOCUMENT) {
282                                    event = parts.get(part).get(reader).next();
283                                }
284                            }
285                        }
286                        break;
287                    case STATE_END_ELEMENT_PART:
288                        if (++part >= parts.size()) {
289                            state = STATE_END_ELEMENT_WRAPPER;
290                            event = END_ELEMENT;
291                        } else {
292                            state = STATE_START_ELEMENT_PART;
293                            event = START_ELEMENT;
294                            reader = 0;
295                        }
296                        break;
297                    case STATE_END_ELEMENT_WRAPPER:
298                    case STATE_END_DOC:
299                        state = STATE_END_DOC;
300                        event = END_DOCUMENT;
301                        break;
302                }
303                return event;
304            }
305    
306            public QName getName() {
307                switch (state) {
308                    case STATE_START_ELEMENT_WRAPPER:
309                    case STATE_END_ELEMENT_WRAPPER:
310                        return JbiConstants.WSDL11_WRAPPER_MESSAGE;
311                    case STATE_START_ELEMENT_PART:
312                    case STATE_END_ELEMENT_PART:
313                        return JbiConstants.WSDL11_WRAPPER_PART;
314                    case STATE_RUN_PART:
315                        return parts.get(part).get(reader).getName();
316                    default:
317                        throw new IllegalStateException();
318                }
319            }
320    
321            public String getLocalName() {
322                switch (state) {
323                    case STATE_START_ELEMENT_WRAPPER:
324                    case STATE_END_ELEMENT_WRAPPER:
325                        return JbiConstants.WSDL11_WRAPPER_MESSAGE_LOCALNAME;
326                    case STATE_START_ELEMENT_PART:
327                    case STATE_END_ELEMENT_PART:
328                        return JbiConstants.WSDL11_WRAPPER_PART_LOCALNAME;
329                    case STATE_RUN_PART:
330                        return parts.get(part).get(reader).getLocalName();
331                    default:
332                        throw new IllegalStateException();
333                }
334            }
335    
336            public String getNamespaceURI() {
337                switch (state) {
338                    case STATE_START_ELEMENT_WRAPPER:
339                    case STATE_END_ELEMENT_WRAPPER:
340                    case STATE_START_ELEMENT_PART:
341                    case STATE_END_ELEMENT_PART:
342                        return JbiConstants.WSDL11_WRAPPER_NAMESPACE;
343                    case STATE_RUN_PART:
344                        return parts.get(part).get(reader).getNamespaceURI();
345                    default:
346                        throw new IllegalStateException();
347                }
348            }
349    
350            public String getPrefix() {
351                switch (state) {
352                    case STATE_START_ELEMENT_WRAPPER:
353                    case STATE_END_ELEMENT_WRAPPER:
354                    case STATE_START_ELEMENT_PART:
355                    case STATE_END_ELEMENT_PART:
356                        return JbiConstants.WSDL11_WRAPPER_PREFIX;
357                    case STATE_RUN_PART:
358                        return parts.get(part).get(reader).getPrefix();
359                    default:
360                        throw new IllegalStateException();
361                }
362            }
363    
364            public boolean hasName() {
365                return state == STATE_RUN_PART ? parts.get(part).get(reader).isStartElement() : (event == START_ELEMENT || event == END_ELEMENT);
366            }
367    
368            public Object getProperty(String s) throws IllegalArgumentException {
369                return null;  //To change body of implemented methods use File | Settings | File Templates.
370            }
371    
372            public void require(int i, String s, String s1) throws XMLStreamException {
373                //To change body of implemented methods use File | Settings | File Templates.
374            }
375    
376            public String getElementText() throws XMLStreamException {
377                return null;  //To change body of implemented methods use File | Settings | File Templates.
378            }
379    
380            public int nextTag() throws XMLStreamException {
381                while (hasNext()) {
382                    int e = next();
383                    if (e == START_ELEMENT || e == END_ELEMENT)
384                        return e;
385                }
386                return event;
387            }
388    
389            public boolean hasNext() throws XMLStreamException {
390                return event != END_DOCUMENT;
391            }
392    
393            public void close() throws XMLStreamException {
394                //To change body of implemented methods use File | Settings | File Templates.
395            }
396    
397            public String getNamespaceURI(String s) {
398                return null;  //To change body of implemented methods use File | Settings | File Templates.
399            }
400    
401            public boolean isStartElement() {
402                return state == STATE_RUN_PART ? parts.get(part).get(reader).isStartElement() : event == START_ELEMENT;
403            }
404    
405            public boolean isEndElement() {
406                return state == STATE_RUN_PART ? parts.get(part).get(reader).isEndElement() : event == END_ELEMENT;
407            }
408    
409            public boolean isCharacters() {
410                return state == STATE_RUN_PART ? parts.get(part).get(reader).isCharacters() : event == CHARACTERS;
411            }
412    
413            public boolean isWhiteSpace() {
414                return state == STATE_RUN_PART ? parts.get(part).get(reader).isWhiteSpace() : event == SPACE;
415            }
416    
417            public String getAttributeValue(String s, String s1) {
418                throw new UnsupportedOperationException("Not implemented");
419            }
420    
421            public int getAttributeCount() {
422                switch (state) {
423                    case STATE_START_ELEMENT_WRAPPER:
424                        return 4;
425                    case STATE_START_ELEMENT_PART:
426                        return 0;
427                    case STATE_RUN_PART:
428                        return parts.get(part).get(reader).getAttributeCount();
429                    default:
430                        throw new IllegalStateException();
431                }
432            }
433    
434            public QName getAttributeName(int i) {
435                switch (state) {
436                    case STATE_START_ELEMENT_WRAPPER:
437                        switch (i) {
438                            case 0: return new QName(XMLConstants.XMLNS_ATTRIBUTE_NS_URI,
439                                                     JbiConstants.WSDL11_WRAPPER_MESSAGE_PREFIX,
440                                                     XMLConstants.XMLNS_ATTRIBUTE);
441                            case 1: return new QName(JbiConstants.WSDL11_WRAPPER_TYPE);
442                            case 2: return new QName(JbiConstants.WSDL11_WRAPPER_NAME);
443                            case 3: return new QName(JbiConstants.WSDL11_WRAPPER_VERSION);
444                            default: throw new IllegalStateException();
445                        }
446                    case STATE_RUN_PART:
447                        return parts.get(part).get(reader).getAttributeName(i);
448                    default:
449                        throw new IllegalStateException();
450                }
451            }
452    
453            public String getAttributeNamespace(int i) {
454                switch (state) {
455                    case STATE_START_ELEMENT_WRAPPER:
456                        switch (i) {
457                            case 0: return XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
458                            case 1:
459                            case 2:
460                            case 3: return XMLConstants.NULL_NS_URI;
461                            default: throw new IllegalStateException();
462                        }
463                    case STATE_RUN_PART:
464                        return parts.get(part).get(reader).getAttributeNamespace(i);
465                    default:
466                        throw new IllegalStateException();
467                }
468            }
469    
470            public String getAttributeLocalName(int i) {
471                switch (state) {
472                    case STATE_START_ELEMENT_WRAPPER:
473                        switch (i) {
474                            case 0: return JbiConstants.WSDL11_WRAPPER_MESSAGE_PREFIX;
475                            case 1: return JbiConstants.WSDL11_WRAPPER_TYPE;
476                            case 2: return JbiConstants.WSDL11_WRAPPER_NAME;
477                            case 3: return JbiConstants.WSDL11_WRAPPER_VERSION;
478                            default: throw new IllegalStateException();
479                        }
480                    case STATE_RUN_PART:
481                        return parts.get(part).get(reader).getAttributeLocalName(i);
482                    default:
483                        throw new IllegalStateException();
484                }
485            }
486    
487            public String getAttributePrefix(int i) {
488                switch (state) {
489                    case STATE_START_ELEMENT_WRAPPER:
490                        switch (i) {
491                            case 0: return XMLConstants.XMLNS_ATTRIBUTE;
492                            case 1:
493                            case 2:
494                            case 3: return XMLConstants.DEFAULT_NS_PREFIX;
495                            default: throw new IllegalStateException();
496                        }
497                    case STATE_RUN_PART:
498                        return parts.get(part).get(reader).getAttributePrefix(i);
499                    default:
500                        throw new IllegalStateException();
501                }
502            }
503    
504            public String getAttributeType(int i) {
505                return "CDATA";
506            }
507    
508            public String getAttributeValue(int i) {
509                switch (state) {
510                    case STATE_START_ELEMENT_WRAPPER:
511                        switch (i) {
512                            case 0:
513                            {
514                                String typeNamespace = wsdlMessage.getName().getNamespaceURI();
515                                if (typeNamespace == null || typeNamespace.length() == 0) {
516                                    throw new IllegalArgumentException("messageType namespace is null or empty");
517                                }
518                                return typeNamespace;
519                            }
520                            case 1:
521                            {
522                                //root.setAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":" + JbiConstants.WSDL11_WRAPPER_MESSAGE_PREFIX,
523                                //                  typeNamespace);
524                                String typeLocalName = wsdlMessage.getName().getLocalPart();
525                                if (typeLocalName == null || typeLocalName.length() == 0) {
526                                    throw new IllegalArgumentException("messageType local name is null or empty");
527                                }
528                                return JbiConstants.WSDL11_WRAPPER_MESSAGE_PREFIX + ":" + typeLocalName;
529                            }
530                            case 2:
531                                return wsdlMessage.getMessageName();
532                            case 3:
533                                return "1.0";
534                            default: throw new IllegalStateException();
535                        }
536                    case STATE_RUN_PART:
537                        return parts.get(part).get(reader).getAttributeValue(i);
538                    default:
539                        throw new IllegalStateException();
540                }
541            }
542    
543            public boolean isAttributeSpecified(int i) {
544                throw new UnsupportedOperationException("Not implemented");
545            }
546    
547            public int getNamespaceCount() {
548                return 0;
549            }
550    
551            public String getNamespacePrefix(int i) {
552                throw new UnsupportedOperationException("Not implemented");
553            }
554    
555            public String getNamespaceURI(int i) {
556                throw new UnsupportedOperationException("Not implemented");
557            }
558    
559            public NamespaceContext getNamespaceContext() {
560                if (state == STATE_RUN_PART) {
561                    return parts.get(part).get(reader).getNamespaceContext();
562                } else {
563                    return new ExtendedXMLStreamReader.SimpleNamespaceContext();
564                }
565            }
566    
567            public String getText() {
568                if (state == STATE_RUN_PART) {
569                    return parts.get(part).get(reader).getText();
570                } else {
571                    throw new IllegalStateException();
572                }
573            }
574    
575            public char[] getTextCharacters() {
576                if (state == STATE_RUN_PART) {
577                    return parts.get(part).get(reader).getTextCharacters();
578                } else {
579                    throw new IllegalStateException();
580                }
581            }
582    
583            public int getTextCharacters(int i, char[] chars, int i1, int i2) throws XMLStreamException {
584                if (state == STATE_RUN_PART) {
585                    return parts.get(part).get(reader).getTextCharacters(i, chars, i1, i2);
586                } else {
587                    throw new IllegalStateException();
588                }
589            }
590    
591            public int getTextStart() {
592                if (state == STATE_RUN_PART) {
593                    return parts.get(part).get(reader).getTextStart();
594                } else {
595                    throw new IllegalStateException();
596                }
597            }
598    
599            public int getTextLength() {
600                if (state == STATE_RUN_PART) {
601                    return parts.get(part).get(reader).getTextLength();
602                } else {
603                    throw new IllegalStateException();
604                }
605            }
606    
607            public String getEncoding() {
608                throw new UnsupportedOperationException("Not implemented");
609            }
610    
611            public boolean hasText() {
612                if (state == STATE_RUN_PART) {
613                    return parts.get(part).get(reader).hasText();
614                } else {
615                    return false;
616                }
617            }
618    
619            public Location getLocation() {
620                return new Location() {
621                    public int getCharacterOffset() {
622                        return 0;
623                    }
624                    public int getColumnNumber() {
625                        return 0;
626                    }
627                    public int getLineNumber() {
628                        return 0;
629                    }
630                    public String getPublicId() {
631                        return null;
632                    }
633                    public String getSystemId() {
634                        return null;
635                    }
636                };
637            }
638    
639            public String getVersion() {
640                throw new UnsupportedOperationException("Not implemented");
641            }
642    
643            public boolean isStandalone() {
644                throw new UnsupportedOperationException("Not implemented");
645            }
646    
647            public boolean standaloneSet() {
648                throw new UnsupportedOperationException("Not implemented");
649            }
650    
651            public String getCharacterEncodingScheme() {
652                throw new UnsupportedOperationException("Not implemented");
653            }
654    
655            public String getPITarget() {
656                throw new UnsupportedOperationException("Not implemented");
657            }
658    
659            public String getPIData() {
660                throw new UnsupportedOperationException("Not implemented");
661            }
662        }
663    }