001    package railo.transformer.library.tag;
002    
003    import java.io.IOException;
004    import java.io.InputStream;
005    import java.io.Reader;
006    import java.net.URISyntaxException;
007    import java.util.ArrayList;
008    import java.util.Iterator;
009    import java.util.Map;
010    import java.util.Set;
011    
012    import org.xml.sax.Attributes;
013    import org.xml.sax.InputSource;
014    import org.xml.sax.SAXException;
015    import org.xml.sax.XMLReader;
016    import org.xml.sax.helpers.DefaultHandler;
017    
018    import railo.commons.collections.HashTable;
019    import railo.commons.io.IOUtil;
020    import railo.commons.io.SystemUtil;
021    import railo.commons.io.res.Resource;
022    import railo.commons.io.res.filter.ExtensionResourceFilter;
023    import railo.commons.io.res.util.ResourceUtil;
024    import railo.runtime.op.Caster;
025    import railo.runtime.text.xml.XMLUtil;
026    import railo.runtime.type.util.ArrayUtil;
027    
028    /**
029     * Die Klasse TagLibFactory liest die XML Repraesentation einer TLD ein 
030     * und laedt diese in eine Objektstruktur. 
031     * Sie tut dieses mithilfe eines Sax Parser.
032     * Die Klasse kann sowohl einzelne Files oder gar ganze Verzeichnisse von TLD laden.
033     */
034    public final class TagLibFactory extends DefaultHandler {
035            
036            /**
037             * Standart Sax Parser
038             */
039            public final static String DEFAULT_SAX_PARSER="org.apache.xerces.parsers.SAXParser";
040            /**
041             * Field <code>TYPE_CFML</code>
042             */
043            public final static short TYPE_CFML=0;
044            /**
045             * Field <code>TYPE_JSP</code>
046             */
047            public final static short TYPE_JSP=1;
048            
049            //private short type=TYPE_CFML;
050            
051            private XMLReader xmlReader;
052            
053            private static Map hashLib=new HashTable();
054            private static TagLib systemTLD;
055            private TagLib lib=new TagLib();
056            
057            private TagLibTag tag;
058            private boolean insideTag=false;
059            private boolean insideScript=false;
060            
061            private TagLibTagAttr att;
062            private boolean insideAtt=false;
063    
064            private String inside;
065            private StringBuffer content=new StringBuffer();
066            private TagLibTagScript script;
067            // System default tld
068            private final static String TLD_1_0=    "/resource/tld/web-cfmtaglibrary_1_0";
069                    
070    
071            /**
072             * Privater Konstruktor, der als Eingabe die TLD als File Objekt erhaelt.
073             * @param saxParser String Klassenpfad zum Sax Parser.
074             * @param file File Objekt auf die TLD.
075             * @throws TagLibException
076             * @throws IOException 
077             */
078            private TagLibFactory(String saxParser,Resource res) throws TagLibException {
079                    Reader r=null;
080                    try {
081                            InputSource is=new InputSource(r=IOUtil.getReader(res.getInputStream(), null));
082                            is.setSystemId(res.getPath());
083                            init(saxParser,is);
084                    } catch (IOException e) {
085                            throw new TagLibException(e);
086                    }
087                    finally {
088                            IOUtil.closeEL(r);
089                    }
090            }
091    
092            /**
093             * Privater Konstruktor, der als Eingabe die TLD als File Objekt erhaelt.
094             * @param saxParser String Klassenpfad zum Sax Parser.
095             * @param file File Objekt auf die TLD.
096             * @throws TagLibException
097             */
098            private TagLibFactory(String saxParser,InputStream stream) throws TagLibException {
099                    try {
100                            InputSource is=new InputSource(IOUtil.getReader(stream, SystemUtil.getCharset()));
101                            //is.setSystemId(file.toString());
102                            init(saxParser,is);
103                    } catch (IOException e) {
104                            throw new TagLibException(e);
105                    }
106            }
107            
108            /**
109             * Privater Konstruktor nur mit Sax Parser Definition, liest Default TLD vom System ein.
110             * @param saxParser String Klassenpfad zum Sax Parser.
111             * @throws TagLibException
112             */
113            private TagLibFactory(String saxParser) throws TagLibException {
114                    InputSource is=new InputSource(this.getClass().getResourceAsStream(TLD_1_0) );
115                    init(saxParser,is);             
116                    lib.setIsCore(true);
117            }
118            
119            /**
120             * Generelle Initialisierungsmetode der Konstruktoren.
121             * @param saxParser String Klassenpfad zum Sax Parser.
122             * @param  is InputStream auf die TLD.
123             * @throws TagLibException
124             */
125            private void init(String saxParser,InputSource is) throws TagLibException       {
126                    //print.dumpStack();
127                    try {
128                            xmlReader=XMLUtil.createXMLReader(saxParser);
129                            xmlReader.setContentHandler(this);
130                            xmlReader.setErrorHandler(this);
131                            xmlReader.setEntityResolver(new TagLibEntityResolver());
132                            xmlReader.parse(is);
133                    } 
134                    catch (IOException e) {
135                            
136                            //String fileName=is.getSystemId();
137                            //String message="IOException: ";
138                            //if(fileName!=null) message+="In File ["+fileName+"], ";
139                            throw new TagLibException(e);
140                    } 
141                    catch (SAXException e) {
142                            e.printStackTrace();
143                            //String fileName=is.getSystemId();
144                            //String message="SAXException: ";
145                            //if(fileName!=null) message+="In File ["+fileName+"], ";
146                            throw new TagLibException(e);
147                    } 
148                    
149        }
150    
151            /**
152             * Geerbte Methode von org.xml.sax.ContentHandler, 
153             * wird bei durchparsen des XML, beim Auftreten eines Start-Tag aufgerufen.
154             *  
155             * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes)
156             */
157            public void startElement(String uri, String name, String qName, Attributes atts) {
158    
159                    inside=qName;
160                    
161                    
162                    if(qName.equals("tag")) startTag();
163                    else if(qName.equals("attribute")) startAtt();
164                    else if(qName.equals("script")) startScript();
165                    
166            }
167        
168            /**
169             * Geerbte Methode von org.xml.sax.ContentHandler, 
170             * wird bei durchparsen des XML, beim auftreten eines End-Tag aufgerufen.
171             *  
172             * @see org.xml.sax.ContentHandler#endElement(String, String, String)
173             */
174            public void endElement(String uri, String name, String qName) {
175                    setContent(content.toString().trim());
176                    content=new StringBuffer();
177                    inside="";
178                    /*
179                    if(tag!=null && tag.getName().equalsIgnoreCase("input")) {
180                            print.ln(tag.getName()+"-"+att.getName()+":"+inside+"-"+insideTag+"-"+insideAtt);
181                            
182                    }
183                    */
184                    if(qName.equals("tag")) endTag();
185                    else if(qName.equals("attribute")) endAtt();
186                    else if(qName.equals("script")) endScript();
187            }
188            
189            
190        /** 
191         * Geerbte Methode von org.xml.sax.ContentHandler, 
192             * wird bei durchparsen des XML, zum einlesen des Content eines Body Element aufgerufen.
193             * 
194             * @see org.xml.sax.ContentHandler#characters(char[], int, int)
195             */
196            public void characters (char ch[], int start, int length)       {
197                    content.append(new String(ch,start,length));
198            }
199            
200            private void setContent(String value)   {
201            if(insideTag)   {
202                    // Att Args
203                    if(insideAtt)   {
204                            // description?
205                            // Name
206                            if(inside.equals("name")) att.setName(value);
207                                    // Required
208                                    else if(inside.equals("required")) 
209                                            att.setRequired(Caster.toBooleanValue(value,false));
210                                    // Rtexprvalue
211                                    else if(inside.equals("rtexprvalue")) 
212                                            att.setRtexpr(Caster.toBooleanValue(value,false));
213                                    // Type
214                                    else if(inside.equals("type")) att.setType(value);
215                                    // Default-Value
216                                    else if(inside.equals("default-value")) att.setDefaultValue(value);
217                            // status
218                                    else if(inside.equals("status")) att.setStatus(toStatus(value));
219                            // Description
220                                    else if(inside.equals("description")) att.setDescription(value);
221                            // No-Name
222                                    else if(inside.equals("noname")) att.setNoname(Caster.toBooleanValue(value,false));
223                                    // default
224                                    else if(inside.equals("default")) att.isDefault(Caster.toBooleanValue(value,false));
225                                    else if(inside.equals("script-support")) att.setScriptSupport(value);
226                    }
227                    else if(insideScript)   {
228                            // type
229                            if(inside.equals("type")) script.setType(value);
230                            if(inside.equals("rtexprvalue")) script.setRtexpr(Caster.toBooleanValue(value,false));
231                            if(inside.equals("context")) script.setContext(value);
232                                    
233                    }
234                    // Tag Args
235                    else    {
236                        // TODO TEI-class
237                            // Name
238                            if(inside.equals("name"))                               {tag.setName(value);}
239                                    // TAG - Class
240                            else if(inside.equals("tag-class"))     tag.setTagClass(value);
241                            else if(inside.equals("tagclass"))      tag.setTagClass(value);
242                            // status
243                                    else if(inside.equals("status"))        tag.setStatus(toStatus(value));
244                            // TAG - description
245                                    else if(inside.equals("description"))   tag.setDescription(value);
246                            // TTE - Class
247                                    else if(inside.equals("tte-class"))     tag.setTteClass(value);
248                            // TTT - Class
249                                    else if(inside.equals("ttt-class"))     tag.setTttClass(value);
250                                    // TDBT - Class
251                                    else if(inside.equals("tdbt-class"))    tag.setTdbtClass(value);
252                                    // TDBT - Class
253                                    else if(inside.equals("att-class"))     tag.setAttributeEvaluatorClassName(value);
254                                    // Body Content
255                                    else if(inside.equals("body-content") || inside.equals("bodycontent"))  {
256                        tag.setBodyContent(value);
257                    }
258                            //allow-removing-literal
259                                    else if(inside.equals("allow-removing-literal"))  {
260                        tag.setAllowRemovingLiteral(Caster.toBooleanValue(value,false));
261                    }
262                                    else if(inside.equals("att-default-value"))     tag.setAttributeDefaultValue(value);
263                                    
264                            
265                            
266                    // Handle Exceptions
267                                    else if(inside.equals("handle-exception"))      {
268                                            tag.setHandleExceptions(Caster.toBooleanValue(value,false));
269                                    }
270                                    // Appendix
271                                    else if(inside.equals("appendix"))      {
272                                            tag.setAppendix(Caster.toBooleanValue(value,false));
273                                    }
274                            // Body rtexprvalue
275                            else if(inside.equals("body-rtexprvalue")) {
276                                    tag.setParseBody(Caster.toBooleanValue(value,false));
277                            }
278                            // Att - min
279                            else if(inside.equals("attribute-min")) 
280                                    tag.setMin(Integer.parseInt(value));
281                            // Att - max
282                            else if(inside.equals("attribute-max")) 
283                                    tag.setMax(Integer.parseInt(value));
284                            // Att Type
285                                    else if(inside.equals("attribute-type"))        {
286                                            int type=TagLibTag.ATTRIBUTE_TYPE_FIXED;
287                                            if(value.toLowerCase().equals("fix"))type=TagLibTag.ATTRIBUTE_TYPE_FIXED;
288                                            else if(value.toLowerCase().equals("fixed"))type=TagLibTag.ATTRIBUTE_TYPE_FIXED;
289                                            else if(value.toLowerCase().equals("dynamic"))type=TagLibTag.ATTRIBUTE_TYPE_DYNAMIC;
290                                            else if(value.toLowerCase().equals("noname"))type=TagLibTag.ATTRIBUTE_TYPE_NONAME;
291                                            else if(value.toLowerCase().equals("mixed"))type=TagLibTag.ATTRIBUTE_TYPE_MIXED;
292                                            else if(value.toLowerCase().equals("fulldynamic"))type=TagLibTag.ATTRIBUTE_TYPE_DYNAMIC;// deprecated
293                                            tag.setAttributeType(type);
294                                    }
295                    }
296            }
297            // Tag Lib
298            else    {
299                // TagLib Typ
300                if(inside.equals("jspversion")) {
301                    //type=TYPE_JSP;
302                    lib.setType("jsp");
303                }
304                else if(inside.equals("cfml-version")) {
305                    //type=TYPE_CFML;
306                    lib.setType("cfml");
307                }
308                
309                
310                    // EL Class
311                    else if(inside.equals("el-class")) lib.setELClass(value);
312                    // Name-Space
313                    else if(inside.equals("name-space")) lib.setNameSpace(value);
314                    // Name Space Sep
315                    else if(inside.equals("name-space-separator")) lib.setNameSpaceSeperator(value);
316                    // short-name
317                else if(inside.equals("short-name")) lib.setShortName(value);
318                else if(inside.equals("shortname")) lib.setShortName(value);
319                // display-name
320                else if(inside.equals("display-name")) lib.setDisplayName(value);
321                else if(inside.equals("displayname")) lib.setDisplayName(value);
322                // ignore-unknow-tags
323                else if(inside.equals("ignore-unknow-tags")) lib.setIgnoreUnknowTags(Caster.toBooleanValue(value,false));
324                
325                
326                else if(inside.equals("uri")) { 
327                                    try {
328                                            lib.setUri(value);
329                                    } catch (URISyntaxException e) {} 
330                            } 
331                            else if(inside.equals("description")) lib.setDescription(value);                
332                    
333            }
334        }   
335            
336    
337            /**
338             * Wird jedesmal wenn das Tag tag beginnt aufgerufen, um intern in einen anderen Zustand zu gelangen.
339             */
340            private void startTag() {
341            tag=new TagLibTag(lib);
342            insideTag=true;
343        }
344            
345            /**
346             * Wird jedesmal wenn das Tag tag endet aufgerufen, um intern in einen anderen Zustand zu gelangen.
347             */
348            private void endTag()   {
349                    lib.setTag(tag);
350            insideTag=false;
351        }
352            
353            private void startScript()      {
354            script=new TagLibTagScript(tag);
355            insideScript=true;
356        }
357            
358            /**
359             * Wird jedesmal wenn das Tag tag endet aufgerufen, um intern in einen anderen Zustand zu gelangen.
360             */
361            private void endScript()        {
362                    tag.setScript(script);
363            insideScript=false;
364        }
365            
366            
367            
368            /**
369             * Wird jedesmal wenn das Tag attribute beginnt aufgerufen, um intern in einen anderen Zustand zu gelangen.
370             */
371            private void startAtt() {
372            att=new TagLibTagAttr(tag);
373            insideAtt=true;
374        }
375            
376            
377            /**
378             * Wird jedesmal wenn das Tag tag endet aufgerufen, um intern in einen anderen Zustand zu gelangen.
379             */
380            private void endAtt()   {
381            tag.setAttribute(att);
382            insideAtt=false;
383        }
384    
385            /**
386             * Gibt die interne TagLib zurueck.
387             * @return Interne Repraesentation der zu erstellenden TagLib.
388             */
389            private TagLib getLib() {
390                    return lib;
391            }
392            
393            /**
394             * TagLib werden innerhalb der Factory in einer HashMap gecacht,
395             * so das diese einmalig von der Factory geladen werden. 
396             * Diese Methode gibt eine gecachte TagLib anhand dessen key zurueck, 
397             * falls diese noch nicht im Cache existiert, gibt die Methode null zurueck.
398             * 
399             * @param key Absoluter Filepfad zur TLD.
400             * @return TagLib
401             */
402            private static TagLib getHashLib(String key) {
403                    return (TagLib)hashLib.get(key);
404            }
405            
406            /**
407             * Laedt mehrere TagLib's die innerhalb eines Verzeichnisses liegen.
408             * @param dir Verzeichnis im dem die TagLib's liegen.
409             * @return TagLib's als Array
410             * @throws TagLibException
411             */
412            public static TagLib[] loadFromDirectory(Resource dir) throws TagLibException   {
413                    return loadFromDirectory(dir,DEFAULT_SAX_PARSER);
414            }
415            
416            /**
417             * Laedt mehrere TagLib's die innerhalb eines Verzeichnisses liegen.
418             * @param dir Verzeichnis im dem die TagLib's liegen.
419             * @param saxParser Definition des Sax Parser mit dem die TagLib's eingelesen werden sollen.
420             * @return TagLib's als Array
421             * @throws TagLibException
422             */
423            public static TagLib[] loadFromDirectory(Resource dir,String saxParser) throws TagLibException  {
424                    if(!dir.isDirectory())return new TagLib[0];
425                    ArrayList<TagLib> arr=new ArrayList<TagLib>();
426                    
427                    Resource[] files=dir.listResources(new ExtensionResourceFilter("tld"));
428                    for(int i=0;i<files.length;i++)      {
429                            if(files[i].isFile())
430                                    arr.add(TagLibFactory.loadFromFile(files[i],saxParser));
431                                    
432                    }
433                    return arr.toArray(new TagLib[arr.size()]);
434            }
435    
436            /**
437             * Laedt eine einzelne TagLib.
438             * @param file TLD die geladen werden soll.
439             * @return TagLib
440             * @throws TagLibException
441             */
442            public static TagLib loadFromFile(Resource res) throws TagLibException  {
443                    return loadFromFile(res,DEFAULT_SAX_PARSER);
444            }
445    
446            /**
447             * Laedt eine einzelne TagLib.
448             * @param file TLD die geladen werden soll.
449             * @return TagLib
450             * @throws TagLibException
451             */
452            public static TagLib loadFromStream(InputStream is) throws TagLibException      {
453                    return loadFromStream(is,DEFAULT_SAX_PARSER);
454            }
455    
456            /**
457             * Laedt eine einzelne TagLib.
458             * @param file TLD die geladen werden soll.
459             * @param saxParser Definition des Sax Parser mit dem die TagLib eingelsesen werden soll.
460             * @return TagLib
461             * @throws TagLibException
462             */
463            public static TagLib loadFromFile(Resource res,String saxParser) throws TagLibException {
464            
465                    // Read in XML
466                TagLib lib=TagLibFactory.getHashLib(ResourceUtil.getCanonicalPathEL(res));
467                    if(lib==null)   {
468                        lib=new TagLibFactory(saxParser,res).getLib();
469                            TagLibFactory.hashLib.put(ResourceUtil.getCanonicalPathEL(res),lib);
470                    }
471                    lib.setSource(res.toString());
472                    return lib;
473            }
474            /**
475             * Laedt eine einzelne TagLib.
476             * @param file TLD die geladen werden soll.
477             * @param saxParser Definition des Sax Parser mit dem die TagLib eingelsesen werden soll.
478             * @return TagLib
479             * @throws TagLibException
480             */
481            public static TagLib loadFromStream(InputStream is,String saxParser) throws TagLibException     {
482            return new TagLibFactory(saxParser,is).getLib();
483            }
484            
485            /**
486             * Laedt die Systeminterne TLD.
487             * @return FunctionLib
488             * @throws TagLibException
489             */
490            public static TagLib loadFromSystem() throws TagLibException    {
491                    return loadFromSystem(DEFAULT_SAX_PARSER);
492            }
493            
494            /**
495             * Laedt die Systeminterne TLD.
496             * @param saxParser Definition des Sax Parser mit dem die FunctionLib eingelsesen werden soll.
497             * @return FunctionLib
498             * @throws TagLibException
499             */
500            public static TagLib loadFromSystem(String saxParser) throws TagLibException    {
501                    if(systemTLD==null)
502                            systemTLD=new TagLibFactory(saxParser).getLib();
503                    return systemTLD;
504            }
505    
506            public static TagLib[] loadFrom(Resource res) throws TagLibException {
507                    if(res.isDirectory())return loadFromDirectory(res);
508                    if(res.isFile()) return new TagLib[]{loadFromFile(res)};
509                    throw new TagLibException("can not load tag library descriptor from ["+res+"]");
510            }
511    
512            
513            /**
514             * return one FunctionLib contain content of all given Function Libs
515             * @param tlds
516             * @return combined function lib
517             */
518            public static TagLib combineTLDs(TagLib[] tlds){
519                    TagLib tl = new TagLib();
520                    if(ArrayUtil.isEmpty(tlds)) return tl;
521    
522                    setAttributes(tlds[0],tl);
523                    
524                    // add functions
525                    for(int i=0;i<tlds.length;i++){
526                            copyTags(tlds[i],tl);
527                    }
528                    return tl;
529            }
530            
531            public static TagLib combineTLDs(Set tlds){
532                    TagLib newTL = new TagLib(),tmp;
533                    if(tlds.size()==0) return newTL ;
534    
535                    Iterator it = tlds.iterator();
536                    int count=0;
537                    while(it.hasNext()){
538                            tmp=(TagLib) it.next();
539                            if(count++==0) setAttributes(tmp,newTL);
540                            copyTags(tmp,newTL);
541                    }
542                    return newTL;
543            }
544            
545            private static void setAttributes(TagLib extTL, TagLib newTL) {
546                    newTL.setDescription(extTL.getDescription());
547                    newTL.setDisplayName(extTL.getDisplayName());
548                    newTL.setELClass(extTL.getELClass());
549                    newTL.setIsCore(extTL.isCore());
550                    newTL.setNameSpace(extTL.getNameSpace());
551                    newTL.setNameSpaceSeperator(extTL.getNameSpaceSeparator());
552                    newTL.setShortName(extTL.getShortName());
553                    newTL.setSource(extTL.getSource());
554                    newTL.setType(extTL.getType());
555                    newTL.setUri(extTL.getUri());
556                    
557            }
558            
559            private static void copyTags(TagLib extTL, TagLib newTL) {
560                    Iterator it = extTL.getTags().entrySet().iterator();
561                    TagLibTag tlt;
562                    while(it.hasNext()){
563                            tlt= (TagLibTag) ((Map.Entry)it.next()).getValue(); // TODO function must be duplicated because it gets a new FunctionLib assigned
564                            newTL.setTag(tlt);
565                    }
566            }
567            
568    
569            public static short toStatus(String value) {
570                    value=value.trim().toLowerCase();
571                    if("deprecated".equals(value)) return TagLib.STATUS_DEPRECATED;
572                    if("dep".equals(value)) return TagLib.STATUS_DEPRECATED;
573                    
574                    if("unimplemented".equals(value)) return TagLib.STATUS_UNIMPLEMENTED;
575                    if("unimplemeted".equals(value)) return TagLib.STATUS_UNIMPLEMENTED;
576                    if("notimplemented".equals(value)) return TagLib.STATUS_UNIMPLEMENTED;
577                    if("not-implemented".equals(value)) return TagLib.STATUS_UNIMPLEMENTED;
578                    if("hidden".equals(value)) return TagLib.STATUS_HIDDEN;
579                    
580                    return TagLib.STATUS_IMPLEMENTED;
581            }
582            public static String toStatus(short value) {
583                    switch(value){
584                    case TagLib.STATUS_DEPRECATED: return "deprecated";
585                    case TagLib.STATUS_UNIMPLEMENTED: return "unimplemeted";
586                    case TagLib.STATUS_HIDDEN: return "hidden";
587                    }
588                    return "implemeted";
589            }
590            
591    }
592    
593    
594    
595    
596    
597    
598    
599