View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.view.facelets.compiler;
20  
21  import org.apache.myfaces.config.ConfigFilesXmlValidationUtils;
22  import org.apache.myfaces.shared.config.MyfacesConfig;
23  import org.apache.myfaces.shared.util.ClassUtils;
24  import org.apache.myfaces.spi.FaceletConfigResourceProvider;
25  import org.apache.myfaces.spi.FaceletConfigResourceProviderFactory;
26  import org.apache.myfaces.view.facelets.tag.AbstractTagLibrary;
27  import org.apache.myfaces.view.facelets.tag.TagLibrary;
28  import org.apache.myfaces.view.facelets.tag.composite.CompositeComponentResourceTagHandler;
29  import org.apache.myfaces.view.facelets.tag.composite.CompositeResouceWrapper;
30  import org.apache.myfaces.view.facelets.util.ParameterCheck;
31  import org.apache.myfaces.view.facelets.util.ReflectionUtil;
32  import org.xml.sax.Attributes;
33  import org.xml.sax.InputSource;
34  import org.xml.sax.Locator;
35  import org.xml.sax.SAXException;
36  import org.xml.sax.SAXParseException;
37  import org.xml.sax.XMLReader;
38  import org.xml.sax.helpers.DefaultHandler;
39  
40  import javax.faces.FacesException;
41  import javax.faces.application.Resource;
42  import javax.faces.application.ResourceHandler;
43  import javax.faces.context.ExternalContext;
44  import javax.faces.context.FacesContext;
45  import javax.faces.view.facelets.ComponentConfig;
46  import javax.faces.view.facelets.FaceletHandler;
47  import javax.faces.view.facelets.Tag;
48  import javax.faces.view.facelets.TagConfig;
49  import javax.faces.view.facelets.TagHandler;
50  import javax.xml.parsers.ParserConfigurationException;
51  import javax.xml.parsers.SAXParser;
52  import javax.xml.parsers.SAXParserFactory;
53  import java.io.IOException;
54  import java.io.InputStream;
55  import java.lang.reflect.Method;
56  import java.net.URL;
57  import java.net.URLConnection;
58  import java.util.Collection;
59  import java.util.logging.Level;
60  import java.util.logging.Logger;
61  
62  /**
63   * Handles creating a {@link org.apache.myfaces.view.facelets.tag.TagLibrary TagLibrary} from a {@link java.net.URL URL} source.
64   * 
65   * @author Jacob Hookom
66   * @version $Id: TagLibraryConfig.java 1449028 2013-02-22 13:30:05Z lofwyr $
67   */
68  public final class TagLibraryConfig
69  {
70  
71      //private final static String SUFFIX = ".taglib.xml";
72  
73      //protected final static Logger log = Logger.getLogger("facelets.compiler");
74      protected final static Logger log = Logger.getLogger(TagLibraryConfig.class.getName());
75  
76      private static class TagLibraryImpl extends AbstractTagLibrary
77      {
78          private String _compositeLibraryName;
79          
80          private final ResourceHandler _resourceHandler;
81  
82          public TagLibraryImpl(FacesContext facesContext, String namespace)
83          {
84              super(namespace);
85              _compositeLibraryName = null;
86              _resourceHandler = facesContext.getApplication().getResourceHandler();
87          }
88          
89          @Override
90          public boolean containsTagHandler(String ns, String localName)
91          {
92              boolean result = super.containsTagHandler(ns, localName);
93              
94              if (!result && _compositeLibraryName != null && containsNamespace(ns))
95              {
96                  Resource compositeComponentResource = _resourceHandler.createResource(
97                          localName +".xhtml", _compositeLibraryName);
98                  
99                  if (compositeComponentResource != null)
100                 {
101                     URL url = compositeComponentResource.getURL();
102                     return (url != null);
103                 }
104             }
105             return result;
106         }
107         
108         @Override
109         public TagHandler createTagHandler(String ns, String localName,
110                 TagConfig tag) throws FacesException
111         {
112             TagHandler tagHandler = super.createTagHandler(ns, localName, tag);
113             
114             if (tagHandler == null && _compositeLibraryName != null && containsNamespace(ns))
115             {
116                 String resourceName = localName + ".xhtml";
117                 // MYFACES-3308 If a composite component exists, it requires to 
118                 // be always resolved. In other words, it should always exists a default.
119                 // The call here for resourceHandler.createResource, just try to get
120                 // the Resource and if it does not exists, it just returns null.
121                 // The intention of this code is just create an instance and pass to
122                 // CompositeComponentResourceTagHandler. Then, its values 
123                 // (resourceName, libraryName) will be used to derive the real instance
124                 // to use in a view, based on the locale used.
125                 Resource compositeComponentResource = new CompositeResouceWrapper(
126                     _resourceHandler.createResource(resourceName, _compositeLibraryName));
127                 
128                 if (compositeComponentResource != null)
129                 {
130                     ComponentConfig componentConfig = new ComponentConfigWrapper(tag,
131                             "javax.faces.NamingContainer", null);
132                     
133                     return new CompositeComponentResourceTagHandler(componentConfig, compositeComponentResource);
134                 }
135             }
136             return tagHandler;
137         }
138 
139         public void setCompositeLibrary(String compositeLibraryName)
140         {
141             _compositeLibraryName = compositeLibraryName;
142         }
143 
144         public void putConverter(String name, String id)
145         {
146             ParameterCheck.notNull("name", name);
147             ParameterCheck.notNull("id", id);
148             this.addConverter(name, id);
149         }
150 
151         public void putConverter(String name, String id, Class<? extends TagHandler> handlerClass)
152         {
153             ParameterCheck.notNull("name", name);
154             ParameterCheck.notNull("id", id);
155             ParameterCheck.notNull("handlerClass", handlerClass);
156             this.addConverter(name, id, handlerClass);
157         }
158 
159         public void putValidator(String name, String id)
160         {
161             ParameterCheck.notNull("name", name);
162             ParameterCheck.notNull("id", id);
163             this.addValidator(name, id);
164         }
165 
166         public void putValidator(String name, String id, Class<? extends TagHandler> handlerClass)
167         {
168             ParameterCheck.notNull("name", name);
169             ParameterCheck.notNull("id", id);
170             ParameterCheck.notNull("handlerClass", handlerClass);
171             this.addValidator(name, id, handlerClass);
172         }
173 
174         public void putTagHandler(String name, Class<? extends TagHandler> type)
175         {
176             ParameterCheck.notNull("name", name);
177             ParameterCheck.notNull("type", type);
178             this.addTagHandler(name, type);
179         }
180 
181         public void putComponent(String name, String componentType, String rendererType)
182         {
183             ParameterCheck.notNull("name", name);
184             ParameterCheck.notNull("componentType", componentType);
185             this.addComponent(name, componentType, rendererType);
186         }
187 
188         public void putComponent(String name, String componentType, String rendererType, 
189                                  Class<? extends TagHandler> handlerClass)
190         {
191             ParameterCheck.notNull("name", name);
192             ParameterCheck.notNull("componentType", componentType);
193             ParameterCheck.notNull("handlerClass", handlerClass);
194             this.addComponent(name, componentType, rendererType, handlerClass);
195         }
196 
197         public void putUserTag(String name, URL source)
198         {
199             ParameterCheck.notNull("name", name);
200             ParameterCheck.notNull("source", source);
201             this.addUserTag(name, source);
202         }
203 
204         public void putFunction(String name, Method method)
205         {
206             ParameterCheck.notNull("name", name);
207             ParameterCheck.notNull("method", method);
208             this.addFunction(name, method);
209         }
210         
211         public void putBehavior(String name, String id)
212         {
213             ParameterCheck.notNull("name", name);
214             ParameterCheck.notNull("id", id);
215             this.addBehavior(name, id);
216         }
217         
218         public void putBehavior(String name, String id, Class<? extends TagHandler> handlerClass)
219         {
220             ParameterCheck.notNull("name", name);
221             ParameterCheck.notNull("id", id);
222             ParameterCheck.notNull("handlerClass", handlerClass);
223             this.addBehavior(name, id, handlerClass);
224         }
225     }
226     
227     private static class ComponentConfigWrapper implements ComponentConfig
228     {
229 
230         protected final TagConfig parent;
231 
232         protected final String componentType;
233 
234         protected final String rendererType;
235 
236         public ComponentConfigWrapper(TagConfig parent, String componentType,
237                 String rendererType)
238         {
239             this.parent = parent;
240             this.componentType = componentType;
241             this.rendererType = rendererType;
242         }
243 
244         public String getComponentType()
245         {
246             return this.componentType;
247         }
248 
249         public String getRendererType()
250         {
251             return this.rendererType;
252         }
253 
254         public FaceletHandler getNextHandler()
255         {
256             return this.parent.getNextHandler();
257         }
258 
259         public Tag getTag()
260         {
261             return this.parent.getTag();
262         }
263 
264         public String getTagId()
265         {
266             return this.parent.getTagId();
267         }
268     }    
269     
270     private static class LibraryHandler extends DefaultHandler
271     {
272         private final URL source;
273         
274         private final FacesContext facesContext;
275 
276         private TagLibrary library;
277 
278         private final StringBuffer buffer;
279 
280         private Locator locator;
281 
282         private String tagName;
283 
284         private String converterId;
285 
286         private String validatorId;
287         
288         private String behaviorId;
289 
290         private String componentType;
291 
292         private String rendererType;
293 
294         private String functionName;
295 
296         private Class<? extends TagHandler> handlerClass;
297 
298         private Class<?> functionClass;
299 
300         private String functionSignature;
301         
302         private String compositeLibraryName;
303         
304         public LibraryHandler(FacesContext facesContext, URL source)
305         {
306             this.source = source;
307             this.buffer = new StringBuffer(64);
308             this.facesContext = facesContext;
309         }
310 
311         public TagLibrary getLibrary()
312         {
313             return this.library;
314         }
315 
316         public void endElement(String uri, String localName, String qName) throws SAXException
317         {
318             try
319             {
320                 if ("facelet-taglib".equals(qName))
321                 {
322                     // Nothing to do
323                 }                
324                 else if ("library-class".equals(qName))
325                 {
326                     this.processLibraryClass();
327                 }
328                 else if ("namespace".equals(qName))
329                 {
330                     this.library = new TagLibraryImpl(facesContext, this.captureBuffer());
331                     if (this.compositeLibraryName != null)
332                     {
333                         ((TagLibraryImpl)this.library).setCompositeLibrary(compositeLibraryName);
334                     }
335                 }
336                 else if ("composite-library-name".equals(qName))
337                 {
338                     this.compositeLibraryName = this.captureBuffer();
339                     if (this.library != null)
340                     {
341                         ((TagLibraryImpl)this.library).setCompositeLibrary(compositeLibraryName);
342                     }
343                 }
344                 else if ("component-type".equals(qName))
345                 {
346                     this.componentType = this.captureBuffer();
347                 }
348                 else if ("renderer-type".equals(qName))
349                 {
350                     this.rendererType = this.captureBuffer();
351                 }
352                 else if ("tag-name".equals(qName))
353                 {
354                     this.tagName = this.captureBuffer();
355                 }
356                 else if ("function-name".equals(qName))
357                 {
358                     this.functionName = this.captureBuffer();
359                 }
360                 else if ("function-class".equals(qName))
361                 {
362                     String className = this.captureBuffer();
363                     this.functionClass = createClass(Object.class, className);
364                 }
365                 else if ("description".equals(qName))
366                 {
367                     //Not used
368                 }
369                 else if ("display-name".equals(qName))
370                 {
371                     //Not used
372                 }
373                 else if ("icon".equals(qName))
374                 {
375                     //Not used
376                 }                
377                 else
378                 {
379                     // Make sure there we've seen a namespace element
380                     // before trying any of the following elements to avoid
381                     // obscure NPEs
382                     if (this.library == null)
383                     {
384                         throw new IllegalStateException("No <namespace> element");
385                     }
386 
387                     TagLibraryImpl impl = (TagLibraryImpl) this.library;
388 
389                     if ("tag".equals(qName))
390                     {
391                         if (this.handlerClass != null)
392                         {
393                             impl.putTagHandler(this.tagName, this.handlerClass);
394                         }
395                     }
396                     else if ("handler-class".equals(qName))
397                     {
398                         String cName = this.captureBuffer();
399                         this.handlerClass = createClass(TagHandler.class, cName);
400                     }
401                     else if ("component".equals(qName))
402                     {
403                         if (this.handlerClass != null)
404                         {
405                             impl.putComponent(this.tagName, this.componentType, this.rendererType, this.handlerClass);
406                             this.handlerClass = null;
407                         }
408                         else
409                         {
410                             impl.putComponent(this.tagName, this.componentType, this.rendererType);
411                         }
412                     }
413                     else if ("converter-id".equals(qName))
414                     {
415                         this.converterId = this.captureBuffer();
416                     }
417                     else if ("converter".equals(qName))
418                     {
419                         if (this.handlerClass != null)
420                         {
421                             impl.putConverter(this.tagName, this.converterId, handlerClass);
422                             this.handlerClass = null;
423                         }
424                         else
425                         {
426                             impl.putConverter(this.tagName, this.converterId);
427                         }
428                         this.converterId = null;
429                     }
430                     else if ("validator-id".equals(qName))
431                     {
432                         this.validatorId = this.captureBuffer();
433                     }
434                     else if ("validator".equals(qName))
435                     {
436                         if (this.handlerClass != null)
437                         {
438                             impl.putValidator(this.tagName, this.validatorId, handlerClass);
439                             this.handlerClass = null;
440                         }
441                         else
442                         {
443                             impl.putValidator(this.tagName, this.validatorId);
444                         }
445                         this.validatorId = null;
446                     }
447                     else if ("behavior-id".equals(qName))
448                     {
449                         this.behaviorId = this.captureBuffer();
450                     }
451                     else if ("behavior".equals(qName))
452                     {
453                         if (this.handlerClass != null)
454                         {
455                             impl.putBehavior(this.tagName, this.behaviorId, handlerClass);
456                             this.handlerClass = null;
457                         }
458                         else
459                         {
460                             impl.putBehavior(this.tagName, this.behaviorId);
461                         }
462                         this.behaviorId = null;
463                     }
464                     else if ("source".equals(qName))
465                     {
466                         String path = this.captureBuffer();
467                         URL url = new URL(this.source, path);
468                         impl.putUserTag(this.tagName, url);
469                     }
470                     else if ("function-signature".equals(qName))
471                     {
472                         this.functionSignature = this.captureBuffer();
473                         Method m = createMethod(this.functionClass, this.functionSignature);
474                         impl.putFunction(this.functionName, m);
475                     }
476                 }
477             }
478             catch (Exception e)
479             {
480                 throw new SAXParseException("Error Handling [" + this.source + "@" + this.locator.getLineNumber()
481                         + "," + this.locator.getColumnNumber() + "] <" + qName + ">", locator, e);
482             }
483         }
484 
485         private String captureBuffer() throws Exception
486         {
487             String s = this.buffer.toString().trim();
488             if (s.length() == 0)
489             {
490                 throw new Exception("Value Cannot be Empty");
491             }
492             this.buffer.setLength(0);
493             return s;
494         }
495 
496         @SuppressWarnings("unchecked")
497         private static <T> Class<? extends T> createClass(Class<T> type, String name) throws Exception
498         {
499             Class<? extends T> factory = (Class<? extends T>)ReflectionUtil.forName(name);
500             if (!type.isAssignableFrom(factory))
501             {
502                 throw new Exception(name + " must be an instance of " + type.getName());
503             }
504             return factory;
505         }
506 
507         private static Method createMethod(Class<?> type, String s) throws Exception
508         {
509             int pos = s.indexOf(' ');
510             if (pos == -1)
511             {
512                 throw new Exception("Must Provide Return Type: " + s);
513             }
514             else
515             {
516                 int pos2 = s.indexOf('(', pos + 1);
517                 if (pos2 == -1)
518                 {
519                     throw new Exception("Must provide a method name, followed by '(': " + s);
520                 }
521                 else
522                 {
523                     String mn = s.substring(pos + 1, pos2).trim();
524                     pos = s.indexOf(')', pos2 + 1);
525                     if (pos == -1)
526                     {
527                         throw new Exception("Must close parentheses, ')' missing: " + s);
528                     }
529                     else
530                     {
531                         String[] ps = s.substring(pos2 + 1, pos).trim().split(",");
532                         Class<?>[] pc;
533                         if (ps.length == 1 && "".equals(ps[0]))
534                         {
535                             pc = new Class[0];
536                         }
537                         else
538                         {
539                             pc = new Class[ps.length];
540                             for (int i = 0; i < pc.length; i++)
541                             {
542                                 pc[i] = ReflectionUtil.forName(ps[i].trim());
543                             }
544                         }
545                         try
546                         {
547                             return type.getMethod(mn, pc);
548                         }
549                         catch (NoSuchMethodException e)
550                         {
551                             throw new Exception("No Function Found on type: " + type.getName() + " with signature: "
552                                     + s);
553                         }
554 
555                     }
556 
557                 }
558             }
559         }
560 
561         private void processLibraryClass() throws Exception
562         {
563             String name = this.captureBuffer();
564             Class<?> type = createClass(TagLibrary.class, name);
565             this.library = (TagLibrary) type.newInstance();
566         }
567 
568         public InputSource resolveEntity(String publicId, String systemId) throws SAXException
569         {
570             if ("-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN".equals(publicId))
571             {
572                 URL url = ClassUtils.getResource("org/apache/myfaces/resource/facelet-taglib_1_0.dtd");
573                 return new InputSource(url.toExternalForm());
574             }
575             return null;
576         }
577 
578         public void characters(char[] ch, int start, int length) throws SAXException
579         {
580             this.buffer.append(ch, start, length);
581         }
582 
583         public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
584         {
585             this.buffer.setLength(0);
586             if ("tag".equals(qName))
587             {
588                 this.handlerClass = null;
589                 this.componentType = null;
590                 this.rendererType = null;
591                 this.tagName = null;
592             }
593             else if ("function".equals(qName))
594             {
595                 this.functionName = null;
596                 this.functionClass = null;
597                 this.functionSignature = null;
598             }
599         }
600 
601         public void error(SAXParseException e) throws SAXException
602         {
603             throw new SAXException(
604                     "Error Handling [" + this.source + "@" + e.getLineNumber() + "," + e.getColumnNumber() + "]", e);
605         }
606 
607         public void setDocumentLocator(Locator locator)
608         {
609             this.locator = locator;
610         }
611 
612         public void fatalError(SAXParseException e) throws SAXException
613         {
614             throw e;
615         }
616 
617         public void warning(SAXParseException e) throws SAXException
618         {
619             throw e;
620         }
621     }
622 
623     public TagLibraryConfig()
624     {
625         super();
626     }
627 
628     public static TagLibrary create(FacesContext facesContext, URL url) throws IOException
629     {
630         InputStream is = null;
631         TagLibrary t = null;
632         URLConnection conn = null;
633         try
634         {
635             ExternalContext externalContext = facesContext.getExternalContext();
636             boolean schemaValidating = false;
637 
638             // validate XML
639             if (MyfacesConfig.getCurrentInstance(externalContext).isValidateXML())
640             {
641                 String version = ConfigFilesXmlValidationUtils.getFaceletTagLibVersion(url);
642                 if (schemaValidating = "2.0".equals(version))
643                 {
644                     ConfigFilesXmlValidationUtils.validateFaceletTagLibFile(url, externalContext, version);
645                 }
646             }
647             
648             // parse file
649             LibraryHandler handler = new LibraryHandler(facesContext, url);
650             SAXParser parser = createSAXParser(handler, externalContext, schemaValidating);
651             conn = url.openConnection();
652             conn.setUseCaches(false);
653             is = conn.getInputStream();
654             parser.parse(is, handler);
655             t = handler.getLibrary();
656         }
657         catch (SAXException e)
658         {
659             IOException ioe = new IOException("Error parsing [" + url + "]: ");
660             ioe.initCause(e);
661             throw ioe;
662         }
663         catch (ParserConfigurationException e)
664         {
665             IOException ioe = new IOException("Error parsing [" + url + "]: ");
666             ioe.initCause(e);
667             throw ioe;
668         }
669         finally
670         {
671             if (is != null)
672             {
673                 is.close();
674             }
675         }
676         return t;
677     }
678 
679     public void loadImplicit(FacesContext facesContext, Compiler compiler) throws IOException
680     {
681         //URL[] urls = Classpath.search(cl, "META-INF/", SUFFIX);
682         //for (int i = 0; i < urls.length; i++)
683         ExternalContext externalContext = facesContext.getExternalContext();
684         FaceletConfigResourceProvider provider = FaceletConfigResourceProviderFactory.
685             getFacesConfigResourceProviderFactory(externalContext).
686                 createFaceletConfigResourceProvider(externalContext);
687         Collection<URL> urls = provider.getFaceletTagLibConfigurationResources(externalContext);
688         for (URL url : urls)
689         {
690             try
691             {
692                 //TagLibrary tl = create(urls[i]);
693                 TagLibrary tl = create(facesContext, url);
694                 if (tl != null)
695                 {
696                     compiler.addTagLibrary(tl);
697                 }
698                 if (log.isLoggable(Level.FINE))
699                 {
700                     //log.fine("Added Library from: " + urls[i]);
701                     log.fine("Added Library from: " + url);
702                 }
703             }
704             catch (Exception e)
705             {
706                 //log.log(Level.SEVERE, "Error Loading Library: " + urls[i], e);
707                 log.log(Level.SEVERE, "Error Loading Library: " + url, e);
708             }
709         }
710     }
711 
712     private static final SAXParser createSAXParser(LibraryHandler handler, ExternalContext externalContext,
713                                                    boolean schemaValidating)
714             throws SAXException, ParserConfigurationException
715     {
716         SAXParserFactory factory = SAXParserFactory.newInstance();
717 
718         if (MyfacesConfig.getCurrentInstance(externalContext).isValidateXML() && !schemaValidating)
719         {
720             // DTD validating
721             factory.setNamespaceAware(false);
722             factory.setFeature("http://xml.org/sax/features/validation", true);
723             factory.setValidating(true);
724         }
725         else
726         {
727             //Just parse it and do not validate, because it is not necessary.
728             factory.setNamespaceAware(true);
729             factory.setFeature("http://xml.org/sax/features/validation", false);
730             factory.setValidating(false);
731         }
732 
733         SAXParser parser = factory.newSAXParser();
734         XMLReader reader = parser.getXMLReader();
735         reader.setErrorHandler(handler);
736         reader.setEntityResolver(handler);
737         return parser;
738     }
739 
740 }