View Javadoc
1   package org.codehaus.mojo.webstart;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with work for additional information
7    * regarding copyright ownership.  The ASF licenses file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.commons.collections.CollectionUtils;
23  import org.apache.commons.lang.StringUtils;
24  import org.apache.maven.artifact.Artifact;
25  import org.apache.maven.artifact.resolver.filter.AndArtifactFilter;
26  import org.apache.maven.artifact.resolver.filter.InversionArtifactFilter;
27  import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
28  import org.apache.maven.artifact.resolver.filter.TypeArtifactFilter;
29  import org.apache.maven.plugin.MojoExecutionException;
30  import org.apache.maven.plugin.MojoFailureException;
31  import org.apache.maven.plugins.annotations.Component;
32  import org.apache.maven.plugins.annotations.Mojo;
33  import org.apache.maven.plugins.annotations.Parameter;
34  import org.apache.maven.plugins.annotations.ResolutionScope;
35  import org.apache.maven.project.MavenProject;
36  import org.codehaus.mojo.webstart.generator.GeneratorTechnicalConfig;
37  import org.codehaus.mojo.webstart.generator.JarResourceGeneratorConfig;
38  import org.codehaus.mojo.webstart.generator.JarResourcesGenerator;
39  import org.codehaus.mojo.webstart.generator.VersionXmlGenerator;
40  import org.codehaus.mojo.webstart.util.ArtifactUtil;
41  import org.codehaus.mojo.webstart.util.IOUtil;
42  
43  import java.io.File;
44  import java.util.Collection;
45  import java.util.Collections;
46  import java.util.LinkedHashSet;
47  import java.util.List;
48  import java.util.Set;
49  
50  /**
51   * MOJO is tailored for use within a Maven web application project that uses
52   * the JnlpDownloadServlet to serve up the JNLP application.
53   *
54   * @author Kevin Stembridge
55   * @version $Id$
56   * @since 1.0-alpha-2
57   */
58  @Mojo( name = "jnlp-download-servlet", requiresProject = true, inheritByDefault = true,
59         requiresDependencyResolution = ResolutionScope.RUNTIME )
60  public class JnlpDownloadServletMojo
61      extends AbstractBaseJnlpMojo
62  {
63  
64      // ----------------------------------------------------------------------
65      // Constants
66      // ----------------------------------------------------------------------
67  
68      /**
69       * Name of the built-in servlet template to use if none is given.
70       */
71      private static final String BUILT_IN_SERVLET_TEMPLATE_FILENAME = "default-jnlp-servlet-template.vm";
72  
73      /**
74       * Name of the default jnlp extension template to use if user define it in the default template directory.
75       */
76      private static final String SERVLET_TEMPLATE_FILENAME = "servlet-template.vm";
77  
78      // ----------------------------------------------------------------------
79      // Mojo Parameters
80      // ----------------------------------------------------------------------
81  
82      /**
83       * The name of the directory into which the jnlp file and other
84       * artifacts will be stored after processing. directory will be created
85       * directly within the root of the WAR produced by the enclosing project.
86       */
87      @Parameter( property = "jnlp.outputDirectoryName", defaultValue = "webstart" )
88      private String outputDirectoryName;
89  
90      /**
91       * The collection of JnlpFile configuration elements. Each one represents a
92       * JNLP file that is to be generated and deployed within the enclosing
93       * project's WAR artifact. At least one JnlpFile must be specified.
94       */
95      @Parameter( required = true )
96      private List<JnlpFile> jnlpFiles;
97  
98      /**
99       * The configurable collection of jars that are common to all jnlpFile elements declared in
100      * plugin configuration. These jars will be output as jar elements in the resources section of
101      * every generated JNLP file and bundled into the specified output directory of the artifact
102      * produced by the project.
103      */
104     @Parameter
105     private List<JarResource> commonJarResources;
106 
107     /**
108      */
109     @Parameter( defaultValue = "${reactorProjects}", required = true, readonly = true )
110     private List<MavenProject> reactorProjects;
111 
112     // ----------------------------------------------------------------------
113     // Components
114     // ----------------------------------------------------------------------
115 
116     /**
117      * Maven project.
118      */
119     @Component
120     private MavenProject project;
121 
122     // ----------------------------------------------------------------------
123     // Mojo Implementation
124     // ----------------------------------------------------------------------
125 
126     /**
127      * {@inheritDoc}
128      */
129     public void execute()
130         throws MojoExecutionException, MojoFailureException
131     {
132 
133         // ---
134         // Check configuration and get all configured jar resources
135         // ---
136 
137         checkConfiguration();
138 
139         // ---
140         // Prepare working directory layout
141         // ---
142 
143         IOUtil ioUtil = getIoUtil();
144 
145         ioUtil.makeDirectoryIfNecessary( getWorkDirectory() );
146         ioUtil.copyResources( getResourcesDirectory(), getWorkDirectory() );
147 
148         // ---
149         // Resolve common jar resources
150         // ---
151 
152         getLog().info( "-- Prepare commons jar resources" );
153         Set<ResolvedJarResource> resolvedCommonJarResources;
154 
155         if ( CollectionUtils.isEmpty( commonJarResources ) )
156         {
157             resolvedCommonJarResources = Collections.emptySet();
158         }
159         else
160         {
161             resolvedCommonJarResources = resolveJarResources( commonJarResources, null );
162         }
163 
164         Set<ResolvedJarResource> allResolvedJarResources = new LinkedHashSet<ResolvedJarResource>();
165         allResolvedJarResources.addAll( resolvedCommonJarResources );
166 
167         // ---
168         // Resolved jnlpFiles
169         // ---
170 
171         getLog().info( "-- Prepare jnlp files" );
172         Set<ResolvedJnlpFile> resolvedJnlpFiles = new LinkedHashSet<ResolvedJnlpFile>();
173 
174         for ( JnlpFile jnlpFile : jnlpFiles )
175         {
176             verboseLog( "prepare jnlp " + jnlpFile );
177 
178             // resolve jar resources of the jnpl file
179             Set<ResolvedJarResource> resolvedJarResources =
180                 resolveJarResources( jnlpFile.getJarResources(), resolvedCommonJarResources );
181 
182             // keep them (to generate the versions.xml file)
183             allResolvedJarResources.addAll( resolvedJarResources );
184 
185             // create the resolved jnlp file
186             ResolvedJnlpFile resolvedJnlpFile = new ResolvedJnlpFile( jnlpFile, resolvedJarResources );
187             resolvedJnlpFiles.add( resolvedJnlpFile );
188         }
189 
190         // ---
191         // Process collected jars
192         // ---
193 
194         signOrRenameJars();
195 
196         // ---
197         // Generate jnlp files
198         // ---
199 
200         for ( ResolvedJnlpFile jnlpFile : resolvedJnlpFiles )
201         {
202             generateJnlpFile( jnlpFile, getLibPath() );
203         }
204 
205         // ---
206         // Generate version xml file
207         // ---
208 
209         generateVersionXml( allResolvedJarResources );
210 
211         // ---
212         // Copy to final directory
213         // ---
214 
215         //FIXME Should be able to configure this
216         File outputDir = new File( getProject().getBuild().getDirectory(),
217                                    getProject().getBuild().getFinalName() + File.separator + outputDirectoryName );
218 
219         ioUtil.copyDirectoryStructure( getWorkDirectory(), outputDir );
220     }
221 
222     // ----------------------------------------------------------------------
223     // AbstractBaseJnlpMojo implementation
224     // ----------------------------------------------------------------------
225 
226     /**
227      * {@inheritDoc}
228      */
229     public MavenProject getProject()
230     {
231         return project;
232     }
233 
234     // ----------------------------------------------------------------------
235     // Private methods
236     // ----------------------------------------------------------------------
237 
238     /**
239      * Confirms that all plugin configuration provided by the user
240      * in the pom.xml file is valid.
241      *
242      * @throws MojoExecutionException if any user configuration is invalid.
243      */
244     private void checkConfiguration()
245         throws MojoExecutionException
246     {
247         checkDependencyFilenameStrategy();
248 
249         if ( CollectionUtils.isEmpty( jnlpFiles ) )
250         {
251             throw new MojoExecutionException(
252                 "Configuration error: At least one <jnlpFile> element must be specified" );
253         }
254 
255         if ( jnlpFiles.size() == 1 && StringUtils.isEmpty( jnlpFiles.get( 0 ).getOutputFilename() ) )
256         {
257             getLog().debug( "Jnlp output file name not specified in single set of jnlpFiles. " +
258                                 "Using default output file name: launch.jnlp." );
259             jnlpFiles.get( 0 ).setOutputFilename( "launch.jnlp" );
260         }
261 
262         // ---
263         // check Jnlp files configuration
264         // ---
265 
266         Set<String> filenames = new LinkedHashSet<String>( jnlpFiles.size() );
267 
268         for ( JnlpFile jnlpFile : jnlpFiles )
269         {
270             if ( !filenames.add( jnlpFile.getOutputFilename() ) )
271             {
272                 throw new MojoExecutionException( "Configuration error: Unique JNLP filenames must be provided. " +
273                                                       "The following file name appears more than once [" +
274                                                       jnlpFile.getOutputFilename() + "]." );
275             }
276 
277             checkJnlpFileConfiguration( jnlpFile );
278         }
279 
280         if ( CollectionUtils.isNotEmpty( commonJarResources ) )
281         {
282 
283             // ---
284             // --- checkCommonJarResources();
285             // ---
286 
287             for ( JarResource jarResource : commonJarResources )
288             {
289                 checkMandatoryJarResourceFields( jarResource );
290 
291                 if ( jarResource.getMainClass() != null )
292                 {
293                     throw new MojoExecutionException( "Configuration Error: A mainClass must not be specified " +
294                                                           "on a JarResource in the commonJarResources collection." );
295                 }
296             }
297 
298             // ---
299             // check for duplicate jar resources
300             // Checks that any jarResources defined in the jnlpFile elements are not also defined in
301             // commonJarResources.
302             // ---
303 
304             for ( JnlpFile jnlpFile : jnlpFiles )
305             {
306                 for ( JarResource jarResource : jnlpFile.getJarResources() )
307                 {
308                     if ( commonJarResources.contains( jarResource ) )
309                     {
310                         String message = "Configuration Error: The jar resource element for artifact " + jarResource +
311                             " defined in common jar resources is duplicated in the jar " +
312                             "resources configuration of the jnlp file identified by the template file " +
313                             jnlpFile.getInputTemplate() + ".";
314 
315                         throw new MojoExecutionException( message );
316                     }
317                 }
318             }
319         }
320     }
321 
322     /**
323      * Checks the validity of a single jnlpFile configuration element.
324      *
325      * @param jnlpFile The configuration element to be checked.
326      * @throws MojoExecutionException if the config element is invalid.
327      */
328     private void checkJnlpFileConfiguration( JnlpFile jnlpFile )
329         throws MojoExecutionException
330     {
331 
332         if ( StringUtils.isBlank( jnlpFile.getOutputFilename() ) )
333         {
334             throw new MojoExecutionException(
335                 "Configuration error: An outputFilename must be specified for each jnlpFile element" );
336         }
337 
338         if ( StringUtils.isNotBlank( jnlpFile.getTemplateFilename() ) )
339         {
340             getLog().warn(
341                 "jnlpFile.templateFilename is deprecated (since 1.0-beta-5), use now the jnlpFile.inputTemplate instead." );
342             jnlpFile.setInputTemplate( jnlpFile.getTemplateFilename() );
343         }
344 //        if ( StringUtils.isBlank( jnlpFile.getInputTemplate() ) )
345 //        {
346 //            verboseLog(
347 //                "No templateFilename found for " + jnlpFile.getOutputFilename() + ". Will use the default template." );
348 //        }
349 //        else
350 //        {
351 //            File templateFile = new File( getTemplateDirectory(), jnlpFile.getInputTemplate() );
352 //
353 //            if ( !templateFile.isFile() )
354 //            {
355 //                throw new MojoExecutionException(
356 //                    "The specified JNLP template does not exist: [" + templateFile + "]" );
357 //            }
358 //        }
359 
360         List<JarResource> jnlpJarResources = jnlpFile.getJarResources();
361 
362         if ( CollectionUtils.isEmpty( jnlpJarResources ) )
363         {
364             throw new MojoExecutionException(
365                 "Configuration error: A non-empty <jarResources> element must be specified in the plugin " +
366                     "configuration for the JNLP file named [" + jnlpFile.getOutputFilename() + "]" );
367         }
368 
369         // ---
370         // find out the jar resource with a main class (can only get one)
371         // ---
372 
373         JarResource mainJarResource = null;
374 
375         for ( JarResource jarResource : jnlpJarResources )
376         {
377             checkMandatoryJarResourceFields( jarResource );
378 
379             if ( jarResource.getMainClass() != null )
380             {
381                 if ( mainJarResource != null )
382                 {
383 
384                     // alreay found
385                     throw new MojoExecutionException(
386                         "Configuration error: More than one <jarResource> element has been declared " +
387                             "with a <mainClass> element in the configuration for JNLP file [" +
388                             jnlpFile.getOutputFilename() +
389                             "]" );
390                 }
391 
392                 jnlpFile.setMainClass( jarResource.getMainClass() );
393                 mainJarResource = jarResource;
394             }
395         }
396 
397         if ( mainJarResource == null )
398         {
399             throw new MojoExecutionException( "Configuration error: Exactly one <jarResource> element must " +
400                                                   "be declared with a <mainClass> element in the configuration for JNLP file [" +
401                                                   jnlpFile.getOutputFilename() + "]" );
402         }
403     }
404 
405     /**
406      * Checks mandatory files of the given jar resource (says groupId, artificatId or version).
407      *
408      * @param jarResource jar resource to check
409      * @throws MojoExecutionException if one of the mandatory field is missing
410      */
411     private void checkMandatoryJarResourceFields( JarResource jarResource )
412         throws MojoExecutionException
413     {
414 
415         if ( !jarResource.isMandatoryField() )
416         {
417             throw new MojoExecutionException(
418                 "Configuration error: groupId, artifactId or version missing for jarResource[" + jarResource + "]." );
419         }
420 
421     }
422 
423     /**
424      * Resolve artifact of incoming jar resources (user configured ones), check their main class.
425      * <p/>
426      * If must include transitive dependencies, collect them and wrap them as new jar resources.
427      * <p/>
428      * For each collected jar resource, copy his artifact file to lib directory (if it has changed),
429      * fill also his hrefValue if required (jar resource with outputJarVersion filled).
430      *
431      * @param configuredJarResources list of configured jar resources
432      * @param commonJarResources     list of resolved common jar resources (null when resolving common jar resources)
433      * @return the set of resolved jar resources
434      * @throws MojoExecutionException if something bas occurs while retrieving resources
435      */
436     private Set<ResolvedJarResource> resolveJarResources( Collection<JarResource> configuredJarResources,
437                                                           Set<ResolvedJarResource> commonJarResources )
438         throws MojoExecutionException
439     {
440 
441         Set<ResolvedJarResource> collectedJarResources = new LinkedHashSet<ResolvedJarResource>();
442 
443         if ( commonJarResources != null )
444         {
445             collectedJarResources.addAll( commonJarResources );
446         }
447 
448         ArtifactUtil artifactUtil = getArtifactUtil();
449 
450         // artifacts resolved from repositories
451         Set<Artifact> artifacts = new LinkedHashSet<Artifact>();
452 
453         // sibling projects hit from a jar resources (need a special transitive resolution)
454         Set<MavenProject> siblingProjects = new LinkedHashSet<MavenProject>();
455 
456         // for each configured JarResource, create and resolve the corresponding artifact and
457         // check it for the mainClass if specified
458         for ( JarResource jarResource : configuredJarResources )
459         {
460             Artifact artifact = artifactUtil.createArtifact( jarResource );
461 
462             // first try to resolv from reactor
463             MavenProject siblingProject = artifactUtil.resolveFromReactor( artifact, getProject(), reactorProjects );
464             if ( siblingProject == null )
465             {
466                 // try to resolve from repositories
467                 artifactUtil.resolveFromRepositories( artifact, getRemoteRepositories(), getLocalRepository() );
468                 artifacts.add( artifact );
469             }
470             else
471             {
472                 artifact = siblingProject.getArtifact();
473                 siblingProjects.add( siblingProject );
474                 artifacts.add( artifact );
475                 artifact.setResolved( true );
476             }
477 
478             if ( StringUtils.isNotBlank( jarResource.getMainClass() ) )
479             {
480                 // check main class
481 
482                 if ( artifact == null )
483                 {
484                     throw new IllegalStateException(
485                         "Implementation Error: The given jarResource cannot be checked for " +
486                             "a main class until the underlying artifact has been resolved: [" +
487                             jarResource + "]" );
488                 }
489 
490                 boolean containsMainClass = artifactUtil.artifactContainsClass( artifact, jarResource.getMainClass() );
491                 if ( !containsMainClass )
492                 {
493                     throw new MojoExecutionException(
494                         "The jar specified by the following jarResource does not contain the declared main class:" +
495                             jarResource );
496                 }
497             }
498             ResolvedJarResource resolvedJarResource = new ResolvedJarResource( jarResource, artifact );
499             getLog().debug( "Add jarResource (configured): " + jarResource );
500             collectedJarResources.add( resolvedJarResource );
501         }
502 
503         if ( !isExcludeTransitive() )
504         {
505 
506             // prepare artifact filter
507 
508             AndArtifactFilter artifactFilter = new AndArtifactFilter();
509             // restricts to runtime and compile scope
510             artifactFilter.add( new ScopeArtifactFilter( Artifact.SCOPE_RUNTIME ) );
511             // restricts to not pom dependencies
512             artifactFilter.add( new InversionArtifactFilter( new TypeArtifactFilter( "pom" ) ) );
513 
514             // get all transitive dependencies
515 
516             Set<Artifact> transitiveArtifacts =
517                 getArtifactUtil().resolveTransitively( artifacts, siblingProjects, getProject().getArtifact(),
518                                                        getLocalRepository(), getRemoteRepositories(), artifactFilter, getProject().getManagedVersionMap());
519 
520             // for each transitive dependency, wrap it in a JarResource and add it to the collection of
521             // existing jar resources (if not already in)
522             for ( Artifact resolvedArtifact : transitiveArtifacts )
523             {
524 
525                 ResolvedJarResource newJarResource = new ResolvedJarResource( resolvedArtifact );
526 
527                 if ( !collectedJarResources.contains( newJarResource ) )
528                 {
529                     getLog().debug( "Add jarResource (transitive): " + newJarResource );
530                     collectedJarResources.add( newJarResource );
531                 }
532             }
533         }
534 
535         // for each JarResource, copy its artifact to the lib directory if necessary
536         for ( ResolvedJarResource jarResource : collectedJarResources )
537         {
538             Artifact artifact = jarResource.getArtifact();
539 
540             String filenameWithVersion =
541                 getDependencyFilenameStrategy().getDependencyFilename( artifact, false, isUseUniqueVersions() );
542 
543             boolean copied = copyJarAsUnprocessedToDirectoryIfNecessary( artifact.getFile(), getLibDirectory(),
544                                                                          filenameWithVersion );
545 
546             if ( copied )
547             {
548                 String name = artifact.getFile().getName();
549 
550                 verboseLog( "Adding " + name + " to modifiedJnlpArtifacts list." );
551                 getModifiedJnlpArtifacts().add( name.substring( 0, name.lastIndexOf( '.' ) ) );
552             }
553 
554             String filename = getDependencyFilenameStrategy().getDependencyFilename( artifact,
555                                                                                      jarResource.isOutputJarVersion()
556                                                                                          ? null
557                                                                                          : false,
558                                                                                      isUseUniqueVersions() );
559             jarResource.setHrefValue( filename );
560         }
561         return collectedJarResources;
562     }
563 
564     private void generateJnlpFile( ResolvedJnlpFile jnlpFile, String libPath )
565         throws MojoExecutionException
566     {
567 
568         File jnlpOutputFile = new File( getWorkDirectory(), jnlpFile.getOutputFilename() );
569 
570         Set<ResolvedJarResource> jarResources = jnlpFile.getJarResources();
571 
572         // ---
573         // get template directory
574         // ---
575 
576         File templateDirectory;
577 
578         if ( StringUtils.isNotBlank( jnlpFile.getInputTemplateResourcePath() ) )
579         {
580             templateDirectory = new File( jnlpFile.getInputTemplateResourcePath() );
581             getLog().debug( "Use jnlp directory : " + templateDirectory );
582         }
583         else
584         {
585             // use default template directory
586             templateDirectory = getTemplateDirectory();
587             getLog().debug( "Use default template directory : " + templateDirectory );
588         }
589 
590         // ---
591         // get template filename
592         // ---
593 
594         if ( StringUtils.isBlank( jnlpFile.getInputTemplate() ) )
595         {
596             getLog().debug(
597                 "Jnlp servlet template file name not specified. Checking if default output file name exists: " +
598                     SERVLET_TEMPLATE_FILENAME );
599 
600             File templateFile = new File( templateDirectory, SERVLET_TEMPLATE_FILENAME );
601 
602             if ( templateFile.isFile() )
603             {
604                 jnlpFile.setInputTemplate( SERVLET_TEMPLATE_FILENAME );
605             }
606             else
607             {
608                 getLog().debug( "Jnlp servlet template file not found in default location. Using inbuilt one." );
609             }
610         }
611         else
612         {
613             File templateFile = new File( templateDirectory, jnlpFile.getInputTemplate() );
614 
615             if ( !templateFile.isFile() )
616             {
617                 throw new MojoExecutionException(
618                     "The specified JNLP servlet template does not exist: [" + templateFile + "]" );
619             }
620         }
621 
622         String templateFileName = jnlpFile.getInputTemplate();
623 
624         GeneratorTechnicalConfig generatorTechnicalConfig =
625             new GeneratorTechnicalConfig( getProject(), templateDirectory, BUILT_IN_SERVLET_TEMPLATE_FILENAME,
626                                           jnlpOutputFile, templateFileName, jnlpFile.getMainClass(),
627                                           getWebstartJarURLForVelocity(), getEncoding() );
628         JarResourceGeneratorConfig jarResourceGeneratorConfig = new JarResourceGeneratorConfig( jarResources, libPath, getCodebase(), jnlpFile.getProperties(), jnlpFile.getArguments() );
629         JarResourcesGenerator jnlpGenerator =
630             new JarResourcesGenerator( getLog(), generatorTechnicalConfig, jarResourceGeneratorConfig );
631 
632 //        jnlpGenerator.setExtraConfig( new SimpleGeneratorExtraConfig( jnlpFile.getProperties(), getCodebase() ) );
633 
634         try
635         {
636             jnlpGenerator.generate();
637         }
638         catch ( Exception e )
639         {
640             throw new MojoExecutionException(
641                 "The following error occurred attempting to generate " + "the JNLP deployment descriptor: " + e, e );
642         }
643 
644     }
645 
646     /**
647      * Generates a version.xml file for all the jarResources configured either in jnlpFile elements
648      * or in the commonJarResources element.
649      *
650      * @throws MojoExecutionException if could not generate the xml version file
651      */
652     private void generateVersionXml( Set<ResolvedJarResource> jarResources )
653         throws MojoExecutionException
654     {
655 
656         VersionXmlGenerator generator = new VersionXmlGenerator( getEncoding() );
657         generator.generate( getLibDirectory(), jarResources );
658     }
659 
660 //    /**
661 //     * Builds the string to be entered in the href attribute of the jar
662 //     * resource element in the generated JNLP file. will be equal
663 //     * to the artifact file name with the version number stripped out.
664 //     *
665 //     * @param artifact The underlying artifact of the jar resource.
666 //     * @return The href string for the given artifact, never null.
667 //     */
668 //    private String buildHrefValue( Artifact artifact )
669 //    {
670 //        StringBuilder sbuf = new StringBuilder();
671 //        sbuf.append( artifact.getArtifactId() );
672 //
673 //        if ( StringUtils.isNotEmpty( artifact.getClassifier() ) )
674 //        {
675 //            sbuf.append( "-" ).append( artifact.getClassifier() );
676 //        }
677 //
678 //        sbuf.append( "." ).append( artifact.getArtifactHandler().getExtension() );
679 //
680 //        return sbuf.toString();
681 //    }
682 }