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.bindings.http.interceptors;
018    
019    import java.io.ByteArrayOutputStream;
020    import java.io.InputStream;
021    import java.util.ArrayList;
022    import java.util.List;
023    
024    import javax.xml.XMLConstants;
025    import javax.xml.namespace.QName;
026    
027    import org.apache.servicemix.soap.util.DomUtil;
028    import org.apache.servicemix.soap.util.IoUtil;
029    import org.apache.ws.commons.schema.XmlSchemaComplexType;
030    import org.apache.ws.commons.schema.XmlSchemaElement;
031    import org.apache.ws.commons.schema.XmlSchemaSequence;
032    import org.w3c.dom.Document;
033    import org.w3c.dom.Element;
034    import org.w3c.dom.Node;
035    import org.w3c.dom.NodeList;
036    
037    /**
038     * @author <a href=""mailto:gnodet [at] gmail.com">Guillaume Nodet</a>
039     */
040    public class IriDecoderHelper {
041    
042        /**
043         * Simple holder class for a name/value pair.
044         */
045        public static class Param {
046            
047            private final String name;
048            private final String value;
049            
050            public Param(String name, String value) {
051                this.name = name;
052                this.value = value;
053            }
054            /**
055             * @return the name
056             */
057            public String getName() {
058                return name;
059            }
060            /**
061             * @return the value
062             */
063            public String getValue() {
064                return value;
065            }
066            /* (non-Javadoc)
067             * @see java.lang.Object#toString()
068             */
069            @Override
070            public String toString() {
071                return "[" + name + "=" + value + "]";
072            }
073            /* (non-Javadoc)
074             * @see java.lang.Object#hashCode()
075             */
076            @Override
077            public int hashCode() {
078                final int PRIME = 31;
079                int result = 1;
080                result = PRIME * result + ((name == null) ? 0 : name.hashCode());
081                result = PRIME * result + ((value == null) ? 0 : value.hashCode());
082                return result;
083            }
084            /* (non-Javadoc)
085             * @see java.lang.Object#equals(java.lang.Object)
086             */
087            @Override
088            public boolean equals(Object obj) {
089                if (this == obj)
090                    return true;
091                if (getClass() != obj.getClass())
092                    return false;
093                final Param other = (Param) obj;
094                if (name == null) {
095                    if (other.name != null)
096                        return false;
097                } else if (!name.equals(other.name))
098                    return false;
099                if (value == null) {
100                    if (other.value != null)
101                        return false;
102                } else if (!value.equals(other.value))
103                    return false;
104                return true;
105            }
106        }
107        
108        public static List<Param> decodeIri(String uri, String loc) {
109            List<Param> values = new ArrayList<Param>();
110            String path = getUriPath(uri);
111            String locPath = getUriPath(loc);
112            int idx2 = 0;
113            char c;
114            for (int idx1 = 0; idx1 < locPath.length(); idx1++) {
115                c = locPath.charAt(idx1); 
116                if (c == '{') {
117                    if (locPath.charAt(idx1 + 1) == '{') {
118                        // double curly brace
119                        expect(path, idx2++, '{');
120                    } else {
121                        int locEnd = locPath.indexOf('}', idx1);
122                        String name = locPath.substring(idx1 + 1, locEnd);
123                        idx1 = locEnd;
124                        int end = findPartEnd(path, idx2);
125                        String value = path.substring(idx2, end);
126                        idx2 = end;
127                        values.add(new Param(name, value));
128                    }
129                } else {
130                    expect(path, idx2++, c);
131                }
132            }
133            if (idx2 < path.length()) {
134                c = path.charAt(idx2++);
135                if (c == '?') {
136                    int end = path.indexOf('#', idx2);
137                    if (end < 0) {
138                        end = path.length();
139                    }
140                    addParams(path, idx2, end, values);
141                }
142            }
143            return values;
144        }
145        
146        public static void addParams(String input, int start, int stop, List<Param> params) {
147            while (start < stop) {
148                int eq = input.indexOf('=', start);
149                int se = input.indexOf('&', eq);
150                if (se < 0) {
151                    se = stop;
152                }
153                params.add(new Param(input.substring(start, eq), 
154                                     input.substring(eq + 1, se)));
155                start = se + 1;
156            }
157        }
158        
159        /**
160         * 
161         */
162        public static int findPartEnd(String path, int c) {
163            int end = path.length();
164            int i = path.indexOf('/', c);
165            if (i >= c && i < end) {
166                end = i;
167            }
168            i = path.indexOf('?', c);
169            if (i >= c && i < end) {
170                end = i;
171            }
172            return end;
173        }
174        
175        /**
176         * Check that the next character is the one expected 
177         * or throw an exception 
178         */
179        public static void expect(String path, int index, char c) {
180            if (path.charAt(index) != c) {
181                throw new IllegalStateException("Unexpected character '" + c + "' at index " + index);
182            }
183        }
184    
185        /**
186         * Get the path of a given uri, removing the scheme and authority parts
187         */
188        public static String getUriPath(String uri) {
189            int idx = uri.indexOf("://");
190            int idx2 = uri.indexOf('/', idx + 3);
191            return uri.substring(idx2 + 1);
192        }
193    
194        public static String combine(String location, String httpLocation) {
195            if (httpLocation == null) {
196                return location;
197            }
198            if (httpLocation.indexOf("://") != -1) {
199                return httpLocation;
200            }
201            if (location.endsWith("/")) {
202                return location + httpLocation;
203            } else {
204                return location + "/" + httpLocation;
205            }
206        }
207    
208        /**
209         * Create a dom document conformant with the given schema element
210         * with the input parameters.
211         * 
212         * @param element
213         * @param params
214         * @return
215         */
216        public static Document buildDocument(XmlSchemaElement element, List<Param> params) {
217            Document doc = DomUtil.createDocument();
218            XmlSchemaComplexType cplxType = (XmlSchemaComplexType) element.getSchemaType();
219            XmlSchemaSequence seq = (XmlSchemaSequence) cplxType.getParticle();
220            Element e = doc.createElementNS(element.getQName().getNamespaceURI(), element.getQName().getLocalPart());
221            e.setAttribute(XMLConstants.XMLNS_ATTRIBUTE, element.getQName().getNamespaceURI());
222            doc.appendChild(e);
223            for (int i = 0; i < seq.getItems().getCount(); i++) {
224                XmlSchemaElement elChild = (XmlSchemaElement) seq.getItems().getItem(i);
225                Param param = null;
226                for (Param p : params) {
227                    if (p.getName().equals(elChild.getQName().getLocalPart())) {
228                        param = p;
229                        break;
230                    }
231                }
232                Element ec = doc.createElementNS(elChild.getQName().getNamespaceURI(), elChild.getQName().getLocalPart());
233                if (!elChild.getQName().getNamespaceURI().equals(element.getQName().getNamespaceURI())) {
234                    ec.setAttribute(XMLConstants.XMLNS_ATTRIBUTE, elChild.getQName().getNamespaceURI());
235                }
236                if (param != null) {
237                    params.remove(param);
238                    ec.appendChild(doc.createTextNode(param.getValue()));
239                }
240                e.appendChild(ec);
241            }            
242            return doc;
243        }
244        
245        public static Document interopolateParams(Document doc, XmlSchemaElement element, List<Param> params) {
246            XmlSchemaComplexType cplxType = (XmlSchemaComplexType)element.getSchemaType();
247            XmlSchemaSequence seq = (XmlSchemaSequence)cplxType.getParticle();
248            Element root = doc.getDocumentElement();
249            if (root == null) {
250                root = doc.createElementNS(element.getQName().getNamespaceURI(), 
251                                        element.getQName().getLocalPart());
252                root.setAttribute(XMLConstants.XMLNS_ATTRIBUTE, element.getQName().getNamespaceURI());
253                doc.appendChild(root);
254            }
255            
256            for (int i = 0; i < seq.getItems().getCount(); i++) {
257                XmlSchemaElement elChild = (XmlSchemaElement)seq.getItems().getItem(i);
258                Param param = null;
259                for (Param p : params) {
260                    if (p.getName().equals(elChild.getQName().getLocalPart())) {
261                        param = p;
262                        break;
263                    }
264                }
265                if (param == null) {
266                    continue;
267                }
268                
269                Element ec = getElement(root, elChild.getQName());
270                if (ec == null) {
271                    ec = doc.createElementNS(elChild.getQName().getNamespaceURI(), elChild.getQName()
272                                             .getLocalPart());
273                    if (!elChild.getQName().getNamespaceURI().equals(element.getQName().getNamespaceURI())) {
274                        ec.setAttribute(XMLConstants.XMLNS_ATTRIBUTE, elChild.getQName().getNamespaceURI());
275                    }
276                    
277                    // insert the element at the appropriate position
278                    Element insertBeforeEl = getIndexedElement(root, i);
279                    if (insertBeforeEl != null) {
280                        root.insertBefore(ec, insertBeforeEl);
281                    } else {
282                        root.appendChild(ec);
283                    }
284                } else {
285                    NodeList childNodes = ec.getChildNodes();
286                    for (int j = 0; j < childNodes.getLength(); j++) {
287                        Node n = childNodes.item(j);
288                        ec.removeChild(n);
289                    }
290                }
291                
292                if (param != null) {
293                    params.remove(param);
294                    ec.appendChild(doc.createTextNode(param.getValue()));
295                }
296            }
297            return doc;
298        }
299    
300        public static List<Param> decode(String uri, String loc, InputStream is) {
301            List<Param> params = IriDecoderHelper.decodeIri(uri, loc);
302            if (is != null) {
303                ByteArrayOutputStream baos = new ByteArrayOutputStream();
304                IoUtil.copyStream(is, baos);
305                IriDecoderHelper.addParams(baos.toString(), 0, baos.size(), params);
306            }
307            return params;
308        }
309    
310        private static Element getIndexedElement(Element e, int i) {
311            NodeList childNodes = e.getChildNodes();
312            int elNum = 0;
313            for (int j = 0; j < childNodes.getLength(); j++) {
314                Node n = childNodes.item(j);
315                if (n.getNodeType() == Node.ELEMENT_NODE) {
316                    if (i == elNum) {
317                        return (Element) n;
318                    }
319                    elNum++;
320                }
321            }
322            return null;
323        }
324    
325        private static Element getElement(Element element, QName name) {
326            NodeList childNodes = element.getChildNodes();
327            for (int j = 0; j < childNodes.getLength(); j++) {
328                Node n = childNodes.item(j);
329                if (n.getNodeType() == Node.ELEMENT_NODE
330                    && n.getLocalName().equals(name.getLocalPart())
331                    && n.getNamespaceURI().equals(name.getNamespaceURI())) {
332                    return (Element)n;
333                }
334            }
335            return null;
336        }
337    
338    }