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 1723395 2016-01-06 18:05:45Z wtlucy $
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.captureBufferEmptyNull();
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.captureBufferEmptyNull();
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         private String captureBufferEmptyNull() throws Exception
497         {
498             String s = this.buffer.toString().trim();
499             if (s.length() == 0)
500             {
501                 //if is "" just set null instead
502                 s = null;
503             }
504             this.buffer.setLength(0);
505             return s;
506         }   
507 
508         @SuppressWarnings("unchecked")
509         private static <T> Class<? extends T> createClass(Class<T> type, String name) throws Exception
510         {
511             Class<? extends T> factory = (Class<? extends T>)ReflectionUtil.forName(name);
512             if (!type.isAssignableFrom(factory))
513             {
514                 throw new Exception(name + " must be an instance of " + type.getName());
515             }
516             return factory;
517         }
518 
519         private static Method createMethod(Class<?> type, String s) throws Exception
520         {
521             int pos = s.indexOf(' ');
522             if (pos == -1)
523             {
524                 throw new Exception("Must Provide Return Type: " + s);
525             }
526             else
527             {
528                 int pos2 = s.indexOf('(', pos + 1);
529                 if (pos2 == -1)
530                 {
531                     throw new Exception("Must provide a method name, followed by '(': " + s);
532                 }
533                 else
534                 {
535                     String mn = s.substring(pos + 1, pos2).trim();
536                     pos = s.indexOf(')', pos2 + 1);
537                     if (pos == -1)
538                     {
539                         throw new Exception("Must close parentheses, ')' missing: " + s);
540                     }
541                     else
542                     {
543                         String[] ps = s.substring(pos2 + 1, pos).trim().split(",");
544                         Class<?>[] pc;
545                         if (ps.length == 1 && "".equals(ps[0]))
546                         {
547                             pc = new Class[0];
548                         }
549                         else
550                         {
551                             pc = new Class[ps.length];
552                             for (int i = 0; i < pc.length; i++)
553                             {
554                                 pc[i] = ReflectionUtil.forName(ps[i].trim());
555                             }
556                         }
557                         try
558                         {
559                             return type.getMethod(mn, pc);
560                         }
561                         catch (NoSuchMethodException e)
562                         {
563                             throw new Exception("No Function Found on type: " + type.getName() + " with signature: "
564                                     + s);
565                         }
566 
567                     }
568 
569                 }
570             }
571         }
572 
573         private void processLibraryClass() throws Exception
574         {
575             String name = this.captureBuffer();
576             Class<?> type = createClass(TagLibrary.class, name);
577             this.library = (TagLibrary) type.newInstance();
578         }
579 
580         public InputSource resolveEntity(String publicId, String systemId) throws SAXException
581         {
582             if ("-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN".equals(publicId))
583             {
584                 URL url = ClassUtils.getResource("org/apache/myfaces/resource/facelet-taglib_1_0.dtd");
585                 return new InputSource(url.toExternalForm());
586             }
587             return null;
588         }
589 
590         public void characters(char[] ch, int start, int length) throws SAXException
591         {
592             this.buffer.append(ch, start, length);
593         }
594 
595         public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
596         {
597             this.buffer.setLength(0);
598             if ("tag".equals(qName))
599             {
600                 this.handlerClass = null;
601                 this.componentType = null;
602                 this.rendererType = null;
603                 this.tagName = null;
604             }
605             else if ("function".equals(qName))
606             {
607                 this.functionName = null;
608                 this.functionClass = null;
609                 this.functionSignature = null;
610             }
611         }
612 
613         public void error(SAXParseException e) throws SAXException
614         {
615             throw new SAXException(
616                     "Error Handling [" + this.source + "@" + e.getLineNumber() + "," + e.getColumnNumber() + "]", e);
617         }
618 
619         public void setDocumentLocator(Locator locator)
620         {
621             this.locator = locator;
622         }
623 
624         public void fatalError(SAXParseException e) throws SAXException
625         {
626             throw e;
627         }
628 
629         public void warning(SAXParseException e) throws SAXException
630         {
631             throw e;
632         }
633     }
634 
635     public TagLibraryConfig()
636     {
637         super();
638     }
639 
640     public static TagLibrary create(FacesContext facesContext, URL url) throws IOException
641     {
642         InputStream is = null;
643         TagLibrary t = null;
644         URLConnection conn = null;
645         try
646         {
647             ExternalContext externalContext = facesContext.getExternalContext();
648             boolean schemaValidating = false;
649 
650             // validate XML
651             if (MyfacesConfig.getCurrentInstance(externalContext).isValidateXML())
652             {
653                 String version = ConfigFilesXmlValidationUtils.getFaceletTagLibVersion(url);
654                 if (schemaValidating = "2.0".equals(version))
655                 {
656                     ConfigFilesXmlValidationUtils.validateFaceletTagLibFile(url, externalContext, version);
657                 }
658             }
659             
660             // parse file
661             LibraryHandler handler = new LibraryHandler(facesContext, url);
662             SAXParser parser = createSAXParser(handler, externalContext, schemaValidating);
663             conn = url.openConnection();
664             conn.setUseCaches(false);
665             is = conn.getInputStream();
666             parser.parse(is, handler);
667             t = handler.getLibrary();
668         }
669         catch (SAXException e)
670         {
671             IOException ioe = new IOException("Error parsing [" + url + "]: ");
672             ioe.initCause(e);
673             throw ioe;
674         }
675         catch (ParserConfigurationException e)
676         {
677             IOException ioe = new IOException("Error parsing [" + url + "]: ");
678             ioe.initCause(e);
679             throw ioe;
680         }
681         finally
682         {
683             if (is != null)
684             {
685                 is.close();
686             }
687         }
688         return t;
689     }
690 
691     public void loadImplicit(FacesContext facesContext, Compiler compiler) throws IOException
692     {
693         //URL[] urls = Classpath.search(cl, "META-INF/", SUFFIX);
694         //for (int i = 0; i < urls.length; i++)
695         ExternalContext externalContext = facesContext.getExternalContext();
696         FaceletConfigResourceProvider provider = FaceletConfigResourceProviderFactory.
697             getFacesConfigResourceProviderFactory(externalContext).
698                 createFaceletConfigResourceProvider(externalContext);
699         Collection<URL> urls = provider.getFaceletTagLibConfigurationResources(externalContext);
700         for (URL url : urls)
701         {
702             try
703             {
704                 //TagLibrary tl = create(urls[i]);
705                 TagLibrary tl = create(facesContext, url);
706                 if (tl != null)
707                 {
708                     compiler.addTagLibrary(tl);
709                 }
710                 if (log.isLoggable(Level.FINE))
711                 {
712                     //log.fine("Added Library from: " + urls[i]);
713                     log.fine("Added Library from: " + url);
714                 }
715             }
716             catch (Exception e)
717             {
718                 //log.log(Level.SEVERE, "Error Loading Library: " + urls[i], e);
719                 log.log(Level.SEVERE, "Error Loading Library: " + url, e);
720             }
721         }
722     }
723 
724     private static final SAXParser createSAXParser(LibraryHandler handler, ExternalContext externalContext,
725                                                    boolean schemaValidating)
726             throws SAXException, ParserConfigurationException
727     {
728         SAXParserFactory factory = SAXParserFactory.newInstance();
729 
730         if (MyfacesConfig.getCurrentInstance(externalContext).isValidateXML() && !schemaValidating)
731         {
732             // DTD validating
733             factory.setNamespaceAware(false);
734             factory.setFeature("http://xml.org/sax/features/validation", true);
735             factory.setValidating(true);
736         }
737         else
738         {
739             //Just parse it and do not validate, because it is not necessary.
740             factory.setNamespaceAware(true);
741             factory.setFeature("http://xml.org/sax/features/validation", false);
742             factory.setValidating(false);
743         }
744 
745         SAXParser parser = factory.newSAXParser();
746         XMLReader reader = parser.getXMLReader();
747         reader.setErrorHandler(handler);
748         reader.setEntityResolver(handler);
749         return parser;
750     }
751 
752 }