View Javadoc
1   /*
2    * The MIT License
3    * Copyright © 2004-2014 Fabrizio Giustina
4    * Copyright © 2022-2022 Web-Legacy
5    *
6    * Permission is hereby granted, free of charge, to any person obtaining a copy
7    * of this software and associated documentation files (the "Software"), to deal
8    * in the Software without restriction, including without limitation the rights
9    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10   * copies of the Software, and to permit persons to whom the Software is
11   * furnished to do so, subject to the following conditions:
12   *
13   * The above copyright notice and this permission notice shall be included in
14   * all copies or substantial portions of the Software.
15   *
16   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22   * THE SOFTWARE.
23   */
24  package net.sf.maventaglib;
25  
26  import java.io.File;
27  import java.io.FileInputStream;
28  import java.io.FileNotFoundException;
29  import java.io.FileOutputStream;
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.lang.reflect.Method;
33  import java.lang.reflect.Modifier;
34  import java.text.MessageFormat;
35  import java.util.ArrayList;
36  import java.util.HashMap;
37  import java.util.HashSet;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.Set;
41  
42  import javax.xml.parsers.DocumentBuilder;
43  
44  import net.sf.maventaglib.util.XmlHelper;
45  
46  import org.apache.commons.beanutils.PropertyUtils;
47  import org.apache.maven.plugin.AbstractMojo;
48  import org.apache.maven.plugin.MojoExecutionException;
49  import org.apache.maven.plugin.MojoFailureException;
50  import org.apache.maven.plugins.annotations.LifecyclePhase;
51  import org.apache.maven.plugins.annotations.Mojo;
52  import org.apache.maven.plugins.annotations.Parameter;
53  import org.codehaus.plexus.util.FileUtils;
54  import org.jdom2.input.DOMBuilder;
55  import org.jdom2.output.Format;
56  import org.jdom2.output.XMLOutputter;
57  import org.w3c.dom.Document;
58  import org.w3c.dom.Element;
59  
60  import com.sun.tlddoc.tagfileparser.Attribute;
61  import com.sun.tlddoc.tagfileparser.Directive;
62  import com.sun.tlddoc.tagfileparser.javacc.ParseException;
63  import com.sun.tlddoc.tagfileparser.javacc.TagFile;
64  
65  
66  /**
67   * Generates tld files from directories of jsp 2.0 tag files.
68   * @author Fabrizio Giustina
69   * @version $Id: TldGenerateMojo.java 217 2014-08-15 20:50:32Z fgiust $
70   */
71  @Mojo(name="tldgenerate", defaultPhase=LifecyclePhase.GENERATE_RESOURCES)
72  public class TldGenerateMojo extends AbstractMojo
73  {
74  
75      /**
76       * Directory containing tag files. Subdirectories are also processed.
77       */
78      @Parameter(defaultValue="src/main/resources/META-INF/tags/")
79      private File tagDir;
80  
81      /**
82       * Output dir for tld files.
83       */
84      @Parameter(defaultValue="${project.build.outputDirectory}/META-INF", required=true)
85      private File outputDir;
86  
87      /**
88       * Version added to tld files, defaults to project version.
89       */
90      @Parameter(property="project.version", required=true)
91      private String version;
92  
93      /**
94       * Detailed configuration for taglibs for tld generation. Starting with version 2.4 you can configure multiple
95       * taglibs with this attribute, and for each taglib you can add both tagfiles dir than classes with EL functions
96       * (note that EL function support is preliminary, the resulting tld does not include anything in the "description"
97       * attribute (that would require parsing javadocs from sources.
98       * 
99       * <pre>
100      *    &lt;taglibs>
101      *      &lt;taglib>
102      *        &lt;description>A test tld that contains functions&lt;/description>
103      *        &lt;shortName>test&lt;/shortName>
104      *        &lt;uri>testuri&lt;/uri>
105      *        &lt;outputname>testtaglib&lt;/outputname>
106      *        &lt;functionClasses>
107      *          &lt;functionClass>org.apache.commons.lang.StringUtils&lt;/functionClass>
108      *        &lt;/functionClasses>
109      *        &lt;tagdir>src/tagfiles&lt;/tagdir>
110      *      &lt;/taglib>
111      *    &lt;/taglibs>
112      * </pre>
113      */
114     @Parameter
115     private List<Taglib> taglibs;
116 
117     /**
118      * @see org.apache.maven.plugin.Mojo#execute()
119      */
120     public void execute() throws MojoExecutionException, MojoFailureException
121     {
122         getLog().debug(MessageFormat.format(Messages.getString("Taglib.generating.tld"), //$NON-NLS-1$
123             tagDir.getAbsolutePath() ));
124 
125         try
126         {
127             generateTlds();
128         }
129         catch (IOException e)
130         {
131             throw new MojoExecutionException(e.getMessage(), e);
132         }
133 
134     }
135 
136     /**
137      * @throws IOException
138      * @throws MojoExecutionException
139      */
140     private void generateTlds() throws IOException, MojoExecutionException
141     {
142 
143         List<Taglib> taglibsList = new ArrayList<Taglib>();
144 
145         if (taglibs != null)
146         {
147             for (Taglib taglib : taglibs)
148             {
149                 // a drink to get the old-style embedding tests work,
150                 // I should migrate to the new IT test infrastructure
151                 Taglib tlib = new Taglib();
152                 try
153                 {
154                     PropertyUtils.copyProperties(tlib, taglib);
155                 }
156                 catch (Throwable e)
157                 {
158                     throw new MojoExecutionException(e.getMessage(), e);
159                 }
160                 taglibsList.add(tlib);
161             }
162         }
163 
164         if (taglibsList == null || taglibsList.isEmpty())
165         {
166             taglibsList = new ArrayList<Taglib>();
167 
168             // old (pre 2.4) behavior, create taglib configurations from dir
169             if (tagDir.isDirectory())
170             {
171                 // handle tag files. Add any directory containing .tag or .tagx files
172                 List<File> tags = FileUtils.getFiles(tagDir, "**/*.tag", null); //$NON-NLS-1$
173                 tags.addAll(FileUtils.getFiles(tagDir, "**/*.tagx", null)); //$NON-NLS-1$
174 
175                 if (!tags.isEmpty())
176                 {
177                     Set<File> directories = new HashSet<File>();
178                     for (File tag : tags)
179                     {
180                         directories.add(tag.getParentFile());
181                     }
182 
183                     for (File dir : directories)
184                     {
185                         Taglib tlib = new Taglib();
186                         tlib.setTagdir(dir);
187                         tlib.setShortName(dir.getName());
188                         tlib.setOutputname(dir.getName() + ".tld");
189                         tlib.setUri(dir.getName());
190                         tlib.setShortName("Tag library for tag file directory " + dir.getName());
191                         taglibsList.add(tlib);
192                     }
193                 }
194                 else
195                 {
196                     getLog().warn(MessageFormat.format(Messages.getString("Taglib.generating.notfound"), //$NON-NLS-1$
197                         tagDir.getAbsolutePath() ));
198                 }
199             }
200         }
201 
202         try
203         {
204             for (Taglib taglib : taglibsList)
205             {
206                 doTaglib(taglib);
207             }
208         }
209         catch (Exception e)
210         {
211             throw new MojoExecutionException(e.getMessage(), e);
212         }
213     }
214 
215     /**
216      * @param taglib
217      * @throws MojoExecutionException
218      * @throws FileNotFoundException
219      * @throws IOException
220      */
221     private void doTaglib(Taglib taglib) throws MojoExecutionException, FileNotFoundException, IOException
222     {
223         Document doc = getTLDDocument(taglib, XmlHelper.getDocumentBuilder());
224 
225         if (taglib.getShortName() == null)
226         {
227             throw new MojoExecutionException("Missing \"shortName\" parameter for taglib " + taglib);
228         }
229 
230         String tldName = taglib.getOutputname();
231         if (tldName == null)
232         {
233             tldName = taglib.getShortName() + ".tld";
234         }
235         if (!tldName.endsWith(".tld"))
236         {
237             tldName = tldName + ".tld";
238         }
239 
240         File outputFile = new File(outputDir, tldName);
241         if (!(outputDir.mkdirs() || outputDir.isDirectory())) {
242             throw new IOException("Unable to create output directory " + outputDir.getAbsolutePath());
243         }
244 
245         getLog().info(MessageFormat.format(Messages.getString("Taglib.generating.file"), //$NON-NLS-1$
246             tldName, taglib.getShortName() ));
247 
248         try (FileOutputStream fos = new FileOutputStream(outputFile)) 
249         {
250             XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat());
251             outputter.output(new DOMBuilder().build(doc), fos);
252         }
253     }
254 
255     protected Document getTLDDocument(Taglib taglib, DocumentBuilder documentBuilder)
256 
257     {
258         Document result = documentBuilder.newDocument();
259 
260         Element taglibElement = createRootTaglibNode(result, taglib.getDescription(), taglib.getShortName(), taglib
261             .getUri());
262 
263         if (taglib.getTagdir() != null && taglib.getTagdir().isDirectory())
264         {
265 
266             String path = taglib.getTagdir().getAbsolutePath().replace(File.separatorChar, '/');
267             int index = path.indexOf("/META-INF/");
268             if (index != -1)
269             {
270                 path = path.substring(index);
271             }
272             else
273             {
274                 path = "unknown";
275             }
276             if (!path.endsWith("/"))
277             {
278                 path += "/";
279             }
280 
281             File[] files = taglib.getTagdir().listFiles();
282             if (files == null)
283             {
284                 files = new File[0];
285             }
286             for (File tag : files)
287             {
288                 if (!tag.isDirectory()
289                     && (tag.getName().toLowerCase().endsWith(".tag") || tag.getName().toLowerCase().endsWith(".tagx")))
290                 {
291                     String tagName = tag.getName().substring(0, tag.getName().lastIndexOf('.'));
292                     String tagPath = path + tag.getName();
293 
294                     Element tagFileElement = result.createElement("tag-file");
295                     Element nameElement = result.createElement("name");
296                     nameElement.appendChild(result.createTextNode(tagName));
297                     tagFileElement.appendChild(nameElement);
298                     Element pathElement = result.createElement("path");
299                     pathElement.appendChild(result.createTextNode(tagPath));
300                     tagFileElement.appendChild(pathElement);
301                     taglibElement.appendChild(tagFileElement);
302 
303                     // tag-file Subelements
304                     //
305                     // description (optional) A description of the tag.
306                     // display-name (optional) Name intended to be displayed by tools.
307                     // icon (optional) Icon that can be used by tools.
308                     // name The unique tag name.
309                     // path Where to find the tag file implementing this tag, relative to the root of the web
310                     // application or
311                     // the root
312                     // of the JAR file for a tag library packaged in a JAR. This must begin with /WEB-INF/tags/ if the
313                     // tag
314                     // file resides
315                     // in the WAR, or /META-INF/tags/ if the tag file resides in a JAR.
316                     // example (optional) Informal description of an example use of the tag.
317                     // tag-extension (optional) Extensions that provide extra information about the tag for tools.
318 
319                     try (InputStream is = new FileInputStream(tag))
320                     {
321                         TagFile tagFile = TagFile.parse(is);
322 
323                         for (Directive directive : tagFile.getDirectives())
324                         {
325                             if ("tag".equals(directive.getDirectiveName()))
326                             {
327                                 for (Attribute attribute : directive.getAttributes())
328                                 {
329                                     if ("description".equals(attribute.getName())
330                                         || "display-name".equals(attribute.getName())
331                                         || "example".equals(attribute.getName()))
332                                     {
333                                         Element element = result.createElement(attribute.getName());
334                                         element.appendChild(result.createTextNode(attribute.getValue()));
335                                         tagFileElement.appendChild(element);
336                                     }
337 
338                                 }
339 
340                             }
341 
342                         }
343 
344                     }
345                     catch (IOException e)
346                     {
347                         // @todo Auto-generated catch block
348                         e.printStackTrace();
349                     }
350                     catch (ParseException e)
351                     {
352                         // @todo Auto-generated catch block
353                         e.printStackTrace();
354                     }
355 
356                 }
357 
358             }
359         }
360 
361         Map<String, Integer> duplicateFunctions = new HashMap<String, Integer>();
362 
363         if (taglib.getFunctionClasses() != null)
364         {
365             for (String functionClassString : taglib.getFunctionClasses())
366             {
367                 Class<?> functionClass = null;
368                 try
369                 {
370                     functionClass = Class.forName(functionClassString);
371 
372                 }
373                 catch (Throwable e)
374                 {
375                     getLog().error(
376                         "Unable to load function class "
377                             + functionClassString
378                             + ": "
379                             + e.getClass().getName()
380                             + " "
381                             + e.getMessage(),
382                         e);
383                     continue;
384                 }
385                 Method[] declaredMethods = functionClass.getDeclaredMethods();
386 
387                 for (Method method : declaredMethods)
388                 {
389                     if (!Modifier.isStatic(method.getModifiers()) || !Modifier.isPublic(method.getModifiers()))
390                     {
391                         // not a public static method
392                         continue;
393                     }
394 
395                     Element tagFileElement = result.createElement("function");
396 
397                     String functionName = method.getName();
398 
399                     if (duplicateFunctions.containsKey(functionName))
400                     {
401                         int currentNum = duplicateFunctions.get(functionName);
402                         duplicateFunctions.put(functionName, currentNum + 1);
403                         functionName = functionName + currentNum;
404                     }
405                     else
406                     {
407                         duplicateFunctions.put(functionName, 1);
408                     }
409 
410                     Element nameElement = result.createElement("name");
411                     nameElement.appendChild(result.createTextNode(functionName));
412                     tagFileElement.appendChild(nameElement);
413 
414                     Element classElement = result.createElement("function-class");
415                     classElement.appendChild(result.createTextNode(method.getDeclaringClass().getName()));
416                     tagFileElement.appendChild(classElement);
417 
418                     StringBuffer parameterTypesString = new StringBuffer();
419                     Class< ? >[] parameterTypes = method.getParameterTypes();
420 
421                     for (int p = 0; p < parameterTypes.length; p++)
422                     {
423                         Class< ? > param = parameterTypes[p];
424                         parameterTypesString.append(nonPrimitiveName(param.getCanonicalName()));
425                         if (parameterTypes.length - 1 > p)
426                         {
427                             parameterTypesString.append(", ");
428                         }
429                     }
430 
431                     Element signatureElement = result.createElement("function-signature");
432                     signatureElement.appendChild(result.createTextNode(nonPrimitiveName(method
433                         .getReturnType()
434                         .getCanonicalName())
435                         + " "
436                         + method.getName()
437                         + "("
438                         + parameterTypesString.toString()
439                         + ")"));
440                     tagFileElement.appendChild(signatureElement);
441 
442                     taglibElement.appendChild(tagFileElement);
443 
444                 }
445 
446             }
447         }
448 
449         return result;
450     }
451 
452     private String nonPrimitiveName(String string)
453     {
454         if ("int".equals(string))
455         {
456             return Integer.class.getName();
457         }
458         else if ("boolean".equals(string))
459         {
460             return Boolean.class.getName();
461         }
462         else if ("double".equals(string))
463         {
464             return Double.class.getName();
465         }
466         else if ("long".equals(string))
467         {
468             return Long.class.getName();
469         }
470         else if ("char".equals(string))
471         {
472             return Character.class.getName();
473         }
474 
475         return string;
476     }
477 
478     protected Element createRootTaglibNode(Document result, String description, String shortName, String uri)
479     {
480         Element taglibElement = result.createElementNS("http://java.sun.com/xml/ns/javaee", "taglib");
481         taglibElement.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns", "http://java.sun.com/xml/ns/javaee");
482         taglibElement.setAttributeNS(
483             "http://www.w3.org/2000/xmlns/",
484             "xmlns:xsi",
485             "http://www.w3.org/2001/XMLSchema-instance");
486         taglibElement.setAttributeNS(
487             "http://www.w3.org/2001/XMLSchema-instance",
488             "xsi:schemaLocation",
489             "http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd");
490         taglibElement.setAttribute("version", "2.1");
491         result.appendChild(taglibElement);
492 
493         Element descriptionElement = result.createElement("description");
494         descriptionElement.appendChild(result.createTextNode(description));
495         taglibElement.appendChild(descriptionElement);
496 
497         Element tlibVersionElement = result.createElement("tlib-version");
498         tlibVersionElement.appendChild(result.createTextNode(version));
499         taglibElement.appendChild(tlibVersionElement);
500 
501         Element shortNameElement = result.createElement("short-name");
502         shortNameElement.appendChild(result.createTextNode(shortName));
503         taglibElement.appendChild(shortNameElement);
504 
505         Element uriElement = result.createElement("uri");
506         uriElement.appendChild(result.createTextNode(uri));
507         taglibElement.appendChild(uriElement);
508 
509         return taglibElement;
510     }
511 }