001/*
002 * Copyright (C) 2009 Christian Schulte <cs@schulte.it>
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without
006 * modification, are permitted provided that the following conditions
007 * are met:
008 *
009 *   o Redistributions of source code must retain the above copyright
010 *     notice, this list of conditions and the following disclaimer.
011 *
012 *   o Redistributions in binary form must reproduce the above copyright
013 *     notice, this list of conditions and the following disclaimer in
014 *     the documentation and/or other materials provided with the
015 *     distribution.
016 *
017 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
018 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
019 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
020 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
021 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
022 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
023 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
024 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
026 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027 *
028 * $JOMC: MergeModulesCommand.java 5251 2016-04-25 19:46:04Z schulte $
029 *
030 */
031package org.jomc.cli.commands;
032
033import java.io.File;
034import java.io.IOException;
035import java.net.URL;
036import java.util.Arrays;
037import java.util.Enumeration;
038import java.util.Iterator;
039import java.util.List;
040import java.util.Locale;
041import java.util.logging.Level;
042import javax.xml.bind.JAXBElement;
043import javax.xml.bind.JAXBException;
044import javax.xml.bind.Marshaller;
045import javax.xml.bind.Unmarshaller;
046import javax.xml.bind.util.JAXBResult;
047import javax.xml.bind.util.JAXBSource;
048import javax.xml.transform.Transformer;
049import javax.xml.transform.TransformerException;
050import javax.xml.transform.stream.StreamSource;
051import org.apache.commons.cli.CommandLine;
052import org.apache.commons.cli.Option;
053import org.jomc.model.Module;
054import org.jomc.model.Modules;
055import org.jomc.model.ObjectFactory;
056import org.jomc.model.modlet.DefaultModelProvider;
057import org.jomc.modlet.ModelContext;
058import org.jomc.modlet.ModelException;
059import org.jomc.modlet.ModelValidationReport;
060
061/**
062 * {@code merge-modules} command implementation.
063 *
064 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
065 */
066public final class MergeModulesCommand extends AbstractModelCommand
067{
068
069    /**
070     * Creates a new {@code MergeModulesCommand} instance.
071     */
072    public MergeModulesCommand()
073    {
074        super();
075    }
076
077    @Override
078    public org.apache.commons.cli.Options getOptions()
079    {
080        final org.apache.commons.cli.Options options = super.getOptions();
081        Option option = (Option) Options.DOCUMENT_OPTION.clone();
082        option.setRequired( true );
083        options.addOption( option );
084
085        options.addOption( Options.DOCUMENT_ENCODING_OPTION );
086        options.addOption( Options.STYLESHEET_OPTION );
087
088        option = (Option) Options.MODULE_OPTION.clone();
089        option.setRequired( true );
090        options.addOption( option );
091
092        options.addOption( Options.MODULE_VERSION_OPTION );
093        options.addOption( Options.MODULE_VENDOR_OPTION );
094        options.addOption( Options.MODULE_INCLUDES_OPTION );
095        options.addOption( Options.MODULE_EXCLUDES_OPTION );
096        options.addOption( Options.RESOURCES_OPTION );
097        return options;
098    }
099
100    public String getName()
101    {
102        return "merge-modules";
103    }
104
105    public String getAbbreviatedName()
106    {
107        return "mm";
108    }
109
110    public String getShortDescription( final Locale locale )
111    {
112        return Messages.getMessage( "mergeModulesShortDescription" );
113    }
114
115    public String getLongDescription( final Locale locale )
116    {
117        return null;
118    }
119
120    protected void executeCommand( final CommandLine commandLine ) throws CommandExecutionException
121    {
122        if ( commandLine == null )
123        {
124            throw new NullPointerException( "commandLine" );
125        }
126
127        CommandLineClassLoader classLoader = null;
128
129        try
130        {
131            classLoader = new CommandLineClassLoader( commandLine );
132            final Modules modules = new Modules();
133            final ModelContext context = this.createModelContext( commandLine, classLoader );
134            final String model = this.getModel( commandLine );
135            final Marshaller marshaller = context.createMarshaller( model );
136            final Unmarshaller unmarshaller = context.createUnmarshaller( model );
137
138            if ( !commandLine.hasOption( Options.NO_MODLET_RESOURCE_VALIDATION_OPTION.getOpt() ) )
139            {
140                unmarshaller.setSchema( context.createSchema( model ) );
141            }
142
143            File stylesheetFile = null;
144            if ( commandLine.hasOption( Options.STYLESHEET_OPTION.getOpt() ) )
145            {
146                stylesheetFile = new File( commandLine.getOptionValue( Options.STYLESHEET_OPTION.getOpt() ) );
147            }
148
149            String moduleVersion = null;
150            if ( commandLine.hasOption( Options.MODULE_VERSION_OPTION.getOpt() ) )
151            {
152                moduleVersion = commandLine.getOptionValue( Options.MODULE_VERSION_OPTION.getOpt() );
153            }
154
155            String moduleVendor = null;
156            if ( commandLine.hasOption( Options.MODULE_VENDOR_OPTION.getOpt() ) )
157            {
158                moduleVendor = commandLine.getOptionValue( Options.MODULE_VENDOR_OPTION.getOpt() );
159            }
160
161            if ( commandLine.hasOption( Options.DOCUMENTS_OPTION.getOpt() ) )
162            {
163                for ( final File f : this.getDocumentFiles( commandLine ) )
164                {
165                    if ( this.isLoggable( Level.FINEST ) )
166                    {
167                        this.log( Level.FINEST, Messages.getMessage( "readingResource", f.getAbsolutePath() ), null );
168                    }
169
170                    Object o = unmarshaller.unmarshal( f );
171                    if ( o instanceof JAXBElement<?> )
172                    {
173                        o = ( (JAXBElement<?>) o ).getValue();
174                    }
175
176                    if ( o instanceof Module )
177                    {
178                        modules.getModule().add( (Module) o );
179                    }
180                    else if ( o instanceof Modules )
181                    {
182                        modules.getModule().addAll( ( (Modules) o ).getModule() );
183                    }
184                    else if ( this.isLoggable( Level.WARNING ) )
185                    {
186                        this.log( Level.WARNING,
187                                  Messages.getMessage( "failureProcessing", f.getAbsolutePath(), o.toString() ),
188                                  null );
189
190                    }
191                }
192            }
193
194            if ( commandLine.hasOption( Options.CLASSPATH_OPTION.getOpt() ) )
195            {
196                String[] resourceNames = null;
197
198                if ( commandLine.hasOption( Options.RESOURCES_OPTION.getOpt() ) )
199                {
200                    resourceNames = commandLine.getOptionValues( Options.RESOURCES_OPTION.getOpt() );
201                }
202
203                if ( resourceNames == null )
204                {
205                    resourceNames = new String[]
206                    {
207                        DefaultModelProvider.getDefaultModuleLocation()
208                    };
209                }
210
211                for ( final String resource : resourceNames )
212                {
213                    for ( final Enumeration<URL> e = classLoader.getResources( resource ); e.hasMoreElements(); )
214                    {
215                        final URL url = e.nextElement();
216
217                        if ( this.isLoggable( Level.FINEST ) )
218                        {
219                            this.log( Level.FINEST,
220                                      Messages.getMessage( "readingResource", url.toExternalForm() ),
221                                      null );
222
223                        }
224
225                        Object o = unmarshaller.unmarshal( url );
226                        if ( o instanceof JAXBElement<?> )
227                        {
228                            o = ( (JAXBElement<?>) o ).getValue();
229                        }
230
231                        if ( o instanceof Module )
232                        {
233                            modules.getModule().add( (Module) o );
234                        }
235                        else if ( o instanceof Modules )
236                        {
237                            modules.getModule().addAll( ( (Modules) o ).getModule() );
238                        }
239                        else if ( this.isLoggable( Level.WARNING ) )
240                        {
241                            this.log( Level.WARNING,
242                                      Messages.getMessage( "failureProcessing", url.toExternalForm(), o.toString() ),
243                                      null );
244
245                        }
246                    }
247                }
248            }
249
250            if ( commandLine.hasOption( Options.MODULE_INCLUDES_OPTION.getOpt() ) )
251            {
252                final String[] values = commandLine.getOptionValues( Options.MODULE_INCLUDES_OPTION.getOpt() );
253
254                if ( values != null )
255                {
256                    final List<String> includes = Arrays.asList( values );
257
258                    for ( final Iterator<Module> it = modules.getModule().iterator(); it.hasNext(); )
259                    {
260                        final Module m = it.next();
261                        if ( !includes.contains( m.getName() ) )
262                        {
263                            this.log( Level.INFO,
264                                      Messages.getMessage( "moduleExclusionInfo", m.getName() ),
265                                      null );
266
267                            it.remove();
268                        }
269                        else
270                        {
271                            this.log( Level.INFO,
272                                      Messages.getMessage( "moduleInclusionInfo", m.getName() ),
273                                      null );
274
275                        }
276                    }
277                }
278            }
279
280            if ( commandLine.hasOption( Options.MODULE_EXCLUDES_OPTION.getOpt() ) )
281            {
282                final String[] values = commandLine.getOptionValues( Options.MODULE_EXCLUDES_OPTION.getOpt() );
283
284                if ( values != null )
285                {
286                    for ( final String exclude : values )
287                    {
288                        final Module m = modules.getModule( exclude );
289
290                        if ( m != null )
291                        {
292                            this.log( Level.INFO,
293                                      Messages.getMessage( "moduleExclusionInfo", m.getName() ),
294                                      null );
295
296                            modules.getModule().remove( m );
297                        }
298                    }
299                }
300            }
301
302            Module classpathModule = null;
303            if ( !commandLine.hasOption( Options.NO_CLASSPATH_RESOLUTION_OPTION.getOpt() ) )
304            {
305                classpathModule = modules.getClasspathModule( Modules.getDefaultClasspathModuleName(), classLoader );
306                if ( classpathModule != null && modules.getModule( Modules.getDefaultClasspathModuleName() ) == null )
307                {
308                    modules.getModule().add( classpathModule );
309                }
310                else
311                {
312                    classpathModule = null;
313                }
314            }
315
316            final ModelValidationReport validationReport = context.validateModel(
317                model, new JAXBSource( marshaller, new ObjectFactory().createModules( modules ) ) );
318
319            this.log( validationReport, marshaller );
320
321            if ( !validationReport.isModelValid() )
322            {
323                throw new CommandExecutionException( Messages.getMessage( "invalidModel", model ) );
324            }
325
326            if ( classpathModule != null )
327            {
328                modules.getModule().remove( classpathModule );
329            }
330
331            Module mergedModule =
332                modules.getMergedModule( commandLine.getOptionValue( Options.MODULE_OPTION.getOpt() ) );
333
334            mergedModule.setVersion( moduleVersion );
335            mergedModule.setVendor( moduleVendor );
336
337            final File moduleFile = new File( commandLine.getOptionValue( Options.DOCUMENT_OPTION.getOpt() ) );
338
339            if ( stylesheetFile != null )
340            {
341                final Transformer transformer = this.createTransformer( new StreamSource( stylesheetFile ) );
342                final JAXBSource source =
343                    new JAXBSource( marshaller, new ObjectFactory().createModule( mergedModule ) );
344
345                final JAXBResult result = new JAXBResult( unmarshaller );
346                unmarshaller.setSchema( null );
347                transformer.transform( source, result );
348
349                if ( result.getResult() instanceof JAXBElement<?>
350                         && ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Module )
351                {
352                    mergedModule = (Module) ( (JAXBElement<?>) result.getResult() ).getValue();
353                }
354                else
355                {
356                    throw new CommandExecutionException( Messages.getMessage( "illegalTransformationResultError",
357                                                                              stylesheetFile.getAbsolutePath() ) );
358
359                }
360            }
361
362            marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
363
364            if ( commandLine.hasOption( Options.DOCUMENT_ENCODING_OPTION.getOpt() ) )
365            {
366                marshaller.setProperty( Marshaller.JAXB_ENCODING,
367                                        commandLine.getOptionValue( Options.DOCUMENT_ENCODING_OPTION.getOpt() ) );
368
369            }
370
371            marshaller.setSchema( context.createSchema( model ) );
372            marshaller.marshal( new ObjectFactory().createModule( mergedModule ), moduleFile );
373
374            if ( this.isLoggable( Level.INFO ) )
375            {
376                this.log( Level.INFO, Messages.getMessage( "writingResource", moduleFile.getAbsolutePath() ), null );
377            }
378
379            classLoader.close();
380            classLoader = null;
381        }
382        catch ( final IOException e )
383        {
384            throw new CommandExecutionException( Messages.getMessage( e ), e );
385        }
386        catch ( final TransformerException e )
387        {
388            String message = Messages.getMessage( e );
389            if ( message == null )
390            {
391                message = Messages.getMessage( e.getException() );
392            }
393
394            throw new CommandExecutionException( message, e );
395        }
396        catch ( final JAXBException e )
397        {
398            String message = Messages.getMessage( e );
399            if ( message == null )
400            {
401                message = Messages.getMessage( e.getLinkedException() );
402            }
403
404            throw new CommandExecutionException( message, e );
405        }
406        catch ( final ModelException e )
407        {
408            throw new CommandExecutionException( Messages.getMessage( e ), e );
409        }
410        finally
411        {
412            try
413            {
414                if ( classLoader != null )
415                {
416                    classLoader.close();
417                }
418            }
419            catch ( final IOException e )
420            {
421                this.log( Level.SEVERE, Messages.getMessage( e ), e );
422            }
423        }
424    }
425
426}