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