001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.net.rpc.client;
020
021import java.util.ArrayList;
022import java.util.HashMap;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Map;
026import java.util.Vector;
027
028import javax.xml.namespace.QName;
029
030import lucee.commons.lang.Pair;
031import lucee.commons.lang.StringUtil;
032import lucee.runtime.exp.ApplicationException;
033import lucee.runtime.text.xml.ArrayNodeList;
034import lucee.runtime.text.xml.XMLUtil;
035
036import org.apache.axis.Constants;
037import org.apache.axis.wsdl.symbolTable.BaseType;
038import org.apache.axis.wsdl.symbolTable.DefinedType;
039import org.apache.axis.wsdl.symbolTable.ElementDecl;
040import org.apache.axis.wsdl.symbolTable.SymbolTable;
041import org.apache.axis.wsdl.symbolTable.Type;
042import org.w3c.dom.Attr;
043import org.w3c.dom.Element;
044import org.w3c.dom.NamedNodeMap;
045import org.w3c.dom.Node;
046
047public class SOAPUtil {
048        
049        private static QName[] SOAP=new QName[]{
050                Constants.SOAP_ARRAY
051                ,Constants.SOAP_ARRAY12
052                ,Constants.SOAP_ARRAY_ATTRS11 
053                ,Constants.SOAP_ARRAY_ATTRS12
054                ,Constants.SOAP_BASE64
055                ,Constants.SOAP_BASE64BINARY
056                ,Constants.SOAP_BOOLEAN
057                ,Constants.SOAP_BYTE
058                ,Constants.SOAP_COMMON_ATTRS11
059                ,Constants.SOAP_COMMON_ATTRS12
060                ,Constants.SOAP_DECIMAL
061                ,Constants.SOAP_DOCUMENT
062                ,Constants.SOAP_DOUBLE
063                ,Constants.SOAP_ELEMENT
064                ,Constants.SOAP_FLOAT
065                ,Constants.SOAP_INT
066                ,Constants.SOAP_INTEGER
067                ,Constants.SOAP_LONG
068                ,Constants.SOAP_MAP
069                ,Constants.SOAP_SHORT
070                ,Constants.SOAP_STRING
071                ,Constants.SOAP_VECTOR
072        };
073        
074        private static QName[] XSD=new QName[]{
075                Constants.XSD_ANY
076                ,Constants.XSD_ANYSIMPLETYPE
077                ,Constants.XSD_ANYTYPE
078                ,Constants.XSD_ANYURI
079                ,Constants.XSD_BASE64
080                ,Constants.XSD_BOOLEAN
081                ,Constants.XSD_BYTE
082                ,Constants.XSD_DATE
083                ,Constants.XSD_DATETIME
084                ,Constants.XSD_DAY
085                ,Constants.XSD_DECIMAL
086                ,Constants.XSD_DOUBLE
087                ,Constants.XSD_DURATION
088                ,Constants.XSD_ENTITIES
089                ,Constants.XSD_ENTITY
090                ,Constants.XSD_FLOAT
091                ,Constants.XSD_HEXBIN
092                ,Constants.XSD_ID
093                ,Constants.XSD_IDREF
094                ,Constants.XSD_IDREFS
095                ,Constants.XSD_INT
096                ,Constants.XSD_INTEGER
097                ,Constants.XSD_LANGUAGE
098                ,Constants.XSD_LONG
099                ,Constants.XSD_MONTH
100                ,Constants.XSD_MONTHDAY
101                ,Constants.XSD_NAME
102                ,Constants.XSD_NCNAME
103                ,Constants.XSD_NEGATIVEINTEGER
104                ,Constants.XSD_NMTOKEN
105                ,Constants.XSD_NMTOKENS
106                ,Constants.XSD_NONNEGATIVEINTEGER
107                ,Constants.XSD_NONPOSITIVEINTEGER
108                ,Constants.XSD_NORMALIZEDSTRING
109                ,Constants.XSD_NOTATION
110                ,Constants.XSD_POSITIVEINTEGER
111                ,Constants.XSD_QNAME
112                ,Constants.XSD_SCHEMA
113                ,Constants.XSD_SHORT
114                ,Constants.XSD_STRING
115                ,Constants.XSD_TIME
116                ,Constants.XSD_TIMEINSTANT1999
117                ,Constants.XSD_TIMEINSTANT2000
118                ,Constants.XSD_TOKEN
119                ,Constants.XSD_UNSIGNEDBYTE
120                ,Constants.XSD_UNSIGNEDINT
121                ,Constants.XSD_UNSIGNEDLONG
122                ,Constants.XSD_UNSIGNEDSHORT
123                ,Constants.XSD_YEAR
124                ,Constants.XSD_YEARMONTH
125        };
126        
127        
128        public static Vector getTypes(Element body, SymbolTable st ) throws ApplicationException {
129                
130                
131                // get the data
132                List<TempType> hrefs=new ArrayList<SOAPUtil.TempType>();
133                Map<String,TempType> ids=new HashMap<String,SOAPUtil.TempType>();
134                ArrayList<TempType> res = new ArrayList<SOAPUtil.TempType>();
135                toTempTypes(XMLUtil.getChildNodes(body, Node.ELEMENT_NODE).iterator(),res,hrefs,ids,res);
136                
137                // replace href with real data
138                Iterator<TempType> it = hrefs.iterator();
139                TempType href,id;
140                while(it.hasNext()){
141                        href = it.next();
142                        id=ids.get(href.href);
143                        if(StringUtil.isEmpty(id)) throw new ApplicationException("cannot handle href "+href.href);
144                        
145                        href.href=null;
146                        href.id=id.id;
147                        href.prefix=id.prefix;
148                        href.namespace=id.namespace;
149                        href.type=id.type;
150                        href.children=id.children;
151                        id.replicated=true;
152                }
153                
154                
155                // removes replicated types in root
156                it = res.iterator();
157                TempType t;
158                while(it.hasNext()){
159                        t=it.next();
160                        if(t.replicated)it.remove();
161                }
162                
163                // now convert to types
164                return toTypes(res,false);
165        }
166        
167        private static Vector toTypes(List<TempType> res,boolean contained) {
168                Iterator<TempType> it = res.iterator();
169                Vector types=new Vector();
170                Type t;
171                TempType tt;
172                Object o;
173                while(it.hasNext()){
174                        tt = it.next();
175                        o=t=toType(tt);
176                        if(contained)o=new ElementDecl(t,new QName(tt.name));
177                        types.add(o);
178                }
179                return types;
180        }
181
182        private static Type toType(TempType tt) {
183                Type t=toBaseType(tt.prefix, tt.type);
184                if(t==null) t=toDefinedType(tt);
185                
186                
187                return t;
188        }
189
190        private static DefinedType toDefinedType(TempType tt) {
191                if(tt.isArray) {
192                        tt.isArray=false;
193                        DefinedType ref = toDefinedType(tt);
194                        String type=ref.getQName().getLocalPart();
195                        if(type.startsWith("ArrayOf")) type="ArrayOf"+type;
196                        else type="ArrayOf:"+type;
197                        
198                        QName qn = StringUtil.isEmpty(tt.namespace)?new QName(type):new QName(tt.namespace,type);
199                        DefinedType dt=new DefinedType(qn, tt.parent);
200                        dt.setRefType(ref);
201                }
202                
203                QName qn = StringUtil.isEmpty(tt.namespace)?new QName(tt.type):new QName(tt.namespace,tt.type);
204                DefinedType dt=new DefinedType(qn, tt.parent);
205                //DefinedType dt=new DefinedType(new QName("http://rpc.xml.coldfusion",tt.type), tt.parent);
206                dt.setBaseType(false);
207                // children
208                if(tt.children!=null && tt.children.size()>0) {
209                        dt.setContainedElements(toTypes(tt.children,true));
210                }
211                
212                return dt;
213        }
214
215        private static void toTempTypes(Iterator<? extends Node> it,List<TempType> children,List<TempType> hrefs,Map<String,TempType> ids,List<TempType> root) {
216                Element e;
217                TempType t;
218                while(it.hasNext()){
219                        e=(Element)it.next();
220                        if(StringUtil.isEmpty(e.getAttribute("xsi:type")) && StringUtil.isEmpty(e.getAttribute("soapenc:type")) && StringUtil.isEmpty(e.getAttribute("href"))) continue;
221                        t=toTempType(e,hrefs,ids,root);
222                        children.add(t);
223                }
224        }
225        
226        private static TempType toTempType(Element e,List<TempType> hrefs,Map<String,TempType> ids,List<TempType> root) {
227                String name=e.getLocalName();
228                Pair<String, String> arrayType=null;
229                // type and namespace
230                int index;
231                Pair<String, String> type = parseType(e.getAttribute("xsi:type"));
232                
233                // optional values
234                String id=e.getAttribute("id");
235                String href=e.getAttribute("href");
236                String array=e.getAttribute("soapenc:arrayType");
237                
238                // is Array
239                if(!StringUtil.isEmpty(type.getValue()) && !StringUtil.isEmpty(array)) {
240                        arrayType=type;
241                        array=array.trim();
242                        array=array.substring(0,array.indexOf('['));
243                        type = parseType(array);
244                }
245                
246                // namespace
247                String namespace=e.getAttribute("xmlns:"+type.getName());
248                if(StringUtil.isEmpty(namespace)) {
249                        NamedNodeMap attrs = e.getAttributes();
250                        int len = attrs.getLength();
251                Attr attr;
252                for(int i=0;i<len;i++) {
253                        attr=(Attr)attrs.item(i);
254                        if(attr.getName().startsWith("xmlns:"))
255                                namespace=attr.getValue();
256                }
257                }
258                
259                
260                TempType t = new TempType(namespace,name,type.getName(),type.getValue(),id,href,arrayType!=null,e.getParentNode());
261                ArrayNodeList children = XMLUtil.getChildNodes(e, Node.ELEMENT_NODE);
262                if(children!=null && children.size()>0) {
263                        List<TempType> _children = new ArrayList<SOAPUtil.TempType>();
264                        // if no array type, the children are members
265                        if(arrayType==null){
266                                toTempTypes(children.iterator(),_children,hrefs,ids,root);
267                                t.setChildren(_children);
268                                
269                        }
270                        // no members just values in the array
271                        else {
272                                toTempTypes(children.iterator(),_children,hrefs,ids,root);
273                                
274                                // make sure we have every type only once
275                                Map<String, TempType> tmp=new HashMap<String, SOAPUtil.TempType>();
276                                Iterator<TempType> it = _children.iterator();
277                                TempType tt;
278                                while(it.hasNext()){
279                                        tt=it.next();
280                                        tmp.put(tt.prefix+":"+tt.type, tt);
281                                }
282                                
283                                it=tmp.values().iterator();
284                                while(it.hasNext()){
285                                        tt=it.next();
286                                        root.add(tt);
287                                }
288                        }
289                }
290
291                if(!StringUtil.isEmpty(href)) hrefs.add(t);
292                if(!StringUtil.isEmpty(id)) ids.put(id,t);
293                
294                return t;
295                
296        }
297        
298        private static Pair<String,String> parseType(String strType) { 
299                int index;
300                String ns=null;
301                if(!StringUtil.isEmpty(strType) && (index=strType.indexOf(':'))!=-1) {
302                        ns=strType.substring(0,index);
303                        strType=strType.substring(index+1);
304                }
305                return new Pair<String, String>(ns, strType);
306        }
307
308        private static BaseType toBaseType(String ns, String type) {
309                if(StringUtil.isEmpty(ns) || StringUtil.isEmpty(type)) return null;
310                
311                if("xsd".equalsIgnoreCase(ns)) {
312                        for(int i=0;i<XSD.length;i++){
313                                if(XSD[i].getLocalPart().equalsIgnoreCase(type.trim()))
314                                        return new BaseType(XSD[i]);
315                        }
316                }
317                if("soap".equalsIgnoreCase(ns) || "soapenc".equalsIgnoreCase(ns)) {
318                        for(int i=0;i<SOAP.length;i++){
319                                if(SOAP[i].getLocalPart().equalsIgnoreCase(type.trim()))
320                                        return new BaseType(SOAP[i]);
321                        }
322                }
323                return null;
324        }
325        
326        
327
328        public static class TempType {
329
330                public boolean replicated;
331                private String type;
332                private String id;
333                private String prefix;
334                private String namespace;
335                private String name;
336                private String href;
337                private List<TempType> children;
338                private boolean isArray;
339                private Node parent;
340
341                public TempType(String namespace, String name, String prefix, String type,String id,String href, boolean isArray, Node parent) {
342                        if(!StringUtil.isEmpty(href)) {
343                                href=href.trim();
344                                if(href.startsWith("#"))href=href.substring(1);
345                        }
346                        
347                        this.namespace=!StringUtil.isEmpty(namespace)?namespace.trim():null;
348                        this.id=!StringUtil.isEmpty(id)?id.trim():null;
349                        this.type=!StringUtil.isEmpty(type)?type.trim():null;
350                        this.prefix=!StringUtil.isEmpty(prefix)?prefix.trim():null;
351                        this.href=href;
352                        this.name=!StringUtil.isEmpty(name)?name.trim():null;
353                        this.isArray=isArray;
354                        this.parent=parent;
355                }
356
357                public void setChildren(List<TempType> children) {
358                        this.children=children;
359                }
360                public List<TempType> getChildren() {
361                        return children;
362                }
363
364                @Override
365                public String toString() {
366                        StringBuilder sb=new StringBuilder();
367                        if(!StringUtil.isEmpty(name))sb.append("name:").append(name);
368                        if(!StringUtil.isEmpty(id))sb.append(";id:").append(id);
369                        if(!StringUtil.isEmpty(prefix))sb.append(";prefix:").append(prefix);
370                        if(!StringUtil.isEmpty(type))sb.append(";type:").append(type);
371                        if(!StringUtil.isEmpty(href))sb.append(";href:").append(href);
372                        sb.append(";array?:").append(isArray);
373                        
374                        if(children!=null && children.size()>0) {
375                                sb.append(";children:{\n");
376                                Iterator<TempType> it = children.iterator();
377                                while(it.hasNext()){
378                                        sb.append('     ').append(StringUtil.replace(it.next().toString(),"\n","\t\n",false)).append(",\n");
379                                }
380
381                                sb.append("}");
382                        }
383                        
384                        
385                        
386                        return sb.toString();
387                }
388        }
389        
390}