View Javadoc

1   /*
2    *  jDTAUS Core Container Mojo
3    *  Copyright (C) 2005 Christian Schulte
4    *  <cs@schulte.it>
5    *
6    *  This library is free software; you can redistribute it and/or
7    *  modify it under the terms of the GNU Lesser General Public
8    *  License as published by the Free Software Foundation; either
9    *  version 2.1 of the License, or any later version.
10   *
11   *  This library is distributed in the hope that it will be useful,
12   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   *  Lesser General Public License for more details.
15   *
16   *  You should have received a copy of the GNU Lesser General Public
17   *  License along with this library; if not, write to the Free Software
18   *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19   *
20   */
21  package org.jdtaus.core.container.mojo;
22  
23  import java.io.ByteArrayInputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.StringWriter;
27  import java.lang.reflect.Method;
28  import java.util.Collection;
29  import java.util.HashSet;
30  import java.util.Iterator;
31  import java.util.LinkedList;
32  import java.util.Locale;
33  import java.util.Set;
34  import java.util.zip.GZIPInputStream;
35  import javax.xml.bind.JAXBException;
36  import org.apache.bcel.classfile.Attribute;
37  import org.apache.bcel.classfile.ClassParser;
38  import org.apache.bcel.classfile.Constant;
39  import org.apache.bcel.classfile.ConstantUtf8;
40  import org.apache.bcel.classfile.JavaClass;
41  import org.apache.bcel.classfile.Unknown;
42  import org.apache.maven.plugin.MojoExecutionException;
43  import org.apache.maven.plugin.MojoFailureException;
44  import org.jdtaus.core.container.ContainerError;
45  import org.jdtaus.core.container.ContextError;
46  import org.jdtaus.core.container.Dependencies;
47  import org.jdtaus.core.container.Dependency;
48  import org.jdtaus.core.container.Implementation;
49  import org.jdtaus.core.container.Implementations;
50  import org.jdtaus.core.container.MissingDependencyException;
51  import org.jdtaus.core.container.MissingImplementationException;
52  import org.jdtaus.core.container.MissingMessageException;
53  import org.jdtaus.core.container.MissingPropertyException;
54  import org.jdtaus.core.container.MissingSpecificationException;
55  import org.jdtaus.core.container.Model;
56  import org.jdtaus.core.container.ModelError;
57  import org.jdtaus.core.container.ModelFactory;
58  import org.jdtaus.core.container.Module;
59  import org.jdtaus.core.container.Modules;
60  import org.jdtaus.core.container.Property;
61  import org.jdtaus.core.container.Specification;
62  import org.jdtaus.core.container.Specifications;
63  import org.jdtaus.core.container.mojo.model.container.DependencyElement;
64  import org.jdtaus.core.container.mojo.model.container.ImplementationElement;
65  import org.jdtaus.core.container.mojo.model.container.MessageElement;
66  import org.jdtaus.core.container.mojo.model.container.ModelObject;
67  import org.jdtaus.core.container.mojo.model.container.ModuleElement;
68  import org.jdtaus.core.container.mojo.model.container.Multiplicity;
69  import org.jdtaus.core.container.mojo.model.container.PropertyElement;
70  import org.jdtaus.core.container.mojo.model.container.SpecificationElement;
71  import org.jdtaus.core.container.mojo.model.container.SpecificationsElement;
72  
73  /**
74   * Mojo to validate set of modules.
75   *
76   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
77   * @version $JDTAUS: VerifyModelMojo.java 8743 2012-10-07 03:06:20Z schulte $
78   * @goal verify-model
79   * @phase verify
80   * @requiresDependencyResolution test
81   */
82  public class VerifyModelMojo extends AbstractContainerMojo
83  {
84      //--AbstractMojo------------------------------------------------------------
85  
86      /** Flag indicating model violations. */
87      private boolean validModel;
88  
89      /** Creates a new {@code VerifyModelMojo} instance. */
90      public VerifyModelMojo()
91      {
92          super();
93      }
94  
95      public void execute() throws MojoExecutionException, MojoFailureException
96      {
97          // Setup all runtime dependencies to be available for classloading.
98          final ClassLoader mavenLoader =
99              Thread.currentThread().getContextClassLoader();
100 
101         try
102         {
103             Thread.currentThread().setContextClassLoader(
104                 this.getClassLoader( mavenLoader ) );
105 
106             enableThreadContextClassLoader();
107 
108             this.validModel = true;
109             this.assertValidModel( ModelFactory.getModel() );
110 
111             if ( this.validModel )
112             {
113                 this.getLog().info( VerifyModelMojoBundle.getInstance().
114                     getValidModelMessage( Locale.getDefault() ) );
115 
116             }
117             else
118             {
119                 throw new MojoExecutionException(
120                     VerifyModelMojoBundle.getInstance().
121                     getModelViolationsMessage( Locale.getDefault() ) );
122 
123             }
124         }
125         catch ( final ContextError e )
126         {
127             throw new MojoExecutionException( e.getMessage(), e );
128         }
129         catch ( final ContainerError e )
130         {
131             throw new MojoExecutionException( e.getMessage(), e );
132         }
133         catch ( final ModelError e )
134         {
135             throw new MojoExecutionException( e.getMessage(), e );
136         }
137         catch ( final Exception e )
138         {
139             throw new MojoExecutionException( e.getMessage(), e );
140         }
141         finally
142         {
143             disableThreadContextClassLoader();
144             Thread.currentThread().setContextClassLoader( mavenLoader );
145         }
146     }
147 
148     //------------------------------------------------------------AbstractMojo--
149     //--VerifyModelMojo---------------------------------------------------------
150 
151     protected ClassLoader getClassLoader( final ClassLoader parent )
152         throws Exception
153     {
154         return this.getRuntimeClassLoader( parent );
155     }
156 
157     private void assertValidModel( final Model model )
158         throws IOException, ClassNotFoundException, JAXBException
159     {
160         final Specifications specs = model.getModules().getSpecifications();
161         for ( int i = specs.size() - 1; i >= 0; i-- )
162         {
163             final Specification spec = specs.getSpecification( i );
164             final Class specClass =
165                 this.assertClassAvailable( spec.getIdentifier() );
166 
167             if ( specClass != null )
168             {
169                 this.assertCompatibleModel( spec, specClass );
170             }
171         }
172 
173         final Implementations impls = model.getModules().getImplementations();
174         for ( int j = impls.size() - 1; j >= 0; j-- )
175         {
176             final Implementation impl = impls.getImplementation( j );
177             final Class clazz =
178                 this.assertClassAvailable( impl.getIdentifier() );
179 
180             if ( clazz != null )
181             {
182                 this.assertImplementsAllSpecifications(
183                     impl, model.getModules(), clazz );
184 
185                 this.assertCompatibleModel( model, impl, clazz );
186             }
187         }
188 
189         // Check for unresolved dependencies.
190         final Dependencies unresolved = this.getUnresolvedDependencies( model );
191         final Set renderedSpecifications = new HashSet();
192 
193         if ( unresolved.size() > 0 )
194         {
195             this.validModel = false;
196 
197             this.getLog().error(
198                 VerifyModelMojoBundle.getInstance().
199                 getUnresolvedDependenciesMessage( Locale.getDefault() ) );
200 
201             for ( int i = unresolved.size() - 1; i >= 0; i-- )
202             {
203                 final Dependency dep = unresolved.getDependency( i );
204 
205                 if ( !renderedSpecifications.contains(
206                     dep.getSpecification().getIdentifier() ) )
207                 {
208                     this.getLog().error(
209                         "  " + VerifyModelMojoBundle.getInstance().
210                         getUnresolvedDependencyMessage(
211                         Locale.getDefault(),
212                         dep.getSpecification().getIdentifier(),
213                         dep.getSpecification().getModuleName(),
214                         dep.getSpecification().getVersion() ) );
215 
216                     renderedSpecifications.add(
217                         dep.getSpecification().getIdentifier() );
218 
219                 }
220             }
221         }
222     }
223 
224     private Class assertClassAvailable( final String className )
225     {
226         Class clazz = null;
227 
228         try
229         {
230             clazz = Class.forName( className, true, Thread.currentThread().
231                 getContextClassLoader() );
232 
233         }
234         catch ( final ClassNotFoundException e )
235         {
236             this.validModel = false;
237 
238             this.getLog().error(
239                 VerifyModelMojoBundle.getInstance().
240                 getClassNotFoundMessage( Locale.getDefault(), e.getMessage() ) );
241 
242             clazz = null;
243         }
244 
245         return clazz;
246     }
247 
248     private void assertImplementsAllSpecifications(
249         final Implementation impl, final Modules modules, final Class clazz )
250     {
251         final Set allInterfaces = new HashSet();
252         this.getAllImplementedInterfaces( clazz, allInterfaces );
253         final Class[] interfaces = (Class[]) allInterfaces.toArray(
254             new Class[ allInterfaces.size() ] );
255 
256         if ( impl.getImplementedSpecifications().size() > 0 )
257         {
258             for ( int i = interfaces.length - 1; i >= 0; i-- )
259             {
260                 try
261                 {
262                     modules.getSpecification( interfaces[i].getName() ).
263                         getImplementation( impl.getName() );
264 
265                 }
266                 catch ( final MissingImplementationException e )
267                 {
268                     this.validModel = false;
269 
270                     this.getLog().error(
271                         VerifyModelMojoBundle.getInstance().
272                         getMissingImplementedSpecificationMessage(
273                         Locale.getDefault(), impl.getIdentifier(),
274                         interfaces[i].getName() ) );
275 
276                 }
277                 catch ( final MissingSpecificationException e )
278                 {
279                     // Classes are allowed to implement additional interfaces
280                     // not defined in the model.
281                     if ( this.getLog().isDebugEnabled() )
282                     {
283                         this.getLog().debug( e.getMessage() );
284                     }
285                 }
286             }
287         }
288 
289         for ( int i = impl.getImplementedSpecifications().size() - 1; i >= 0;
290               i-- )
291         {
292             final Specification spec = impl.getImplementedSpecifications().
293                 getSpecification( i );
294 
295             final Class specClass =
296                 this.assertClassAvailable( spec.getIdentifier() );
297 
298             boolean isImplemented =
299                 spec.getIdentifier().equals( impl.getIdentifier() );
300 
301             for ( int j = interfaces.length - 1; j >= 0; j-- )
302             {
303                 if ( interfaces[j].getName().equals( spec.getIdentifier() ) )
304                 {
305                     isImplemented = true;
306                     break;
307                 }
308             }
309 
310             if ( !isImplemented )
311             {
312                 final String methodName =
313                     spec.getIdentifier().replace( '.', '_' );
314 
315                 try
316                 {
317                     final Method m =
318                         clazz.getMethod( methodName, new Class[ 0 ] );
319 
320                     if ( m.getReturnType() != specClass )
321                     {
322                         this.validModel = false;
323 
324                         this.getLog().error(
325                             VerifyModelMojoBundle.getInstance().
326                             getMissingInterfaceMessage(
327                             Locale.getDefault(), impl.getIdentifier(),
328                             spec.getIdentifier() ) );
329 
330                     }
331                 }
332                 catch ( final NoSuchMethodException e )
333                 {
334                     this.validModel = false;
335 
336                     this.getLog().error(
337                         VerifyModelMojoBundle.getInstance().
338                         getMissingInterfaceMessage(
339                         Locale.getDefault(), impl.getIdentifier(),
340                         spec.getIdentifier() ) );
341 
342                 }
343             }
344         }
345     }
346 
347     /**
348      * Gets all interfaces implemented by a given class.
349      *
350      * @param clazz the class to get all implemented interfaces of.
351      * @param interfaces set for collecting interfaces recursively.
352      */
353     private void getAllImplementedInterfaces( final Class clazz,
354                                               final Set interfaces )
355     {
356         final Class[] current = clazz.getInterfaces();
357         for ( int i = current.length - 1; i >= 0; i-- )
358         {
359             this.getAllImplementedInterfaces( current[i], interfaces );
360             interfaces.add( current[i] );
361         }
362 
363         if ( clazz.getSuperclass() != null )
364         {
365             this.getAllImplementedInterfaces(
366                 clazz.getSuperclass(), interfaces );
367 
368         }
369     }
370 
371     /**
372      * Gets all unresolved dependencies from a given model.
373      *
374      * @param model the model to gets unresolved dependencies from.
375      *
376      * @throws NullPointerException if {@code model} is {@code null}.
377      */
378     private Dependencies getUnresolvedDependencies( final Model model )
379     {
380         final String unresolvedName = "Unresolved-";
381         final Collection deps = new LinkedList();
382 
383         for ( int m = model.getModules().size() - 1; m >= 0; m-- )
384         {
385             final Module mod = model.getModules().getModule( m );
386 
387             for ( int i = mod.getImplementations().size() - 1; i >= 0; i-- )
388             {
389                 final Implementation impl = mod.getImplementations().
390                     getImplementation( i );
391 
392                 for ( int d = impl.getDependencies().size() - 1; d >= 0; d-- )
393                 {
394                     final Dependency dep = (Dependency) impl.getDependencies().
395                         getDependency( d ).clone();
396 
397                     if ( dep.getImplementation() == null &&
398                          dep.getSpecification().getMultiplicity() ==
399                          Specification.MULTIPLICITY_ONE &&
400                          dep.getSpecification().getImplementations().
401                         size() != 1 )
402                     {
403                         dep.setName(
404                             unresolvedName + "-" + m + "-" + i + "-" + d );
405 
406                         deps.add( dep );
407                     }
408                 }
409             }
410         }
411 
412         final Dependencies dependencies = new Dependencies();
413         dependencies.setDependencies(
414             (Dependency[]) deps.toArray( new Dependency[ deps.size() ] ) );
415 
416         return dependencies;
417     }
418 
419     private void assertCompatibleModel( final Model model,
420                                         final Implementation impl,
421                                         final Class clazz )
422         throws IOException, ClassNotFoundException, JAXBException
423     {
424         final ModuleElement committedModule =
425             this.extractImplementation( clazz );
426 
427         if ( committedModule != null )
428         {
429             ImplementationElement committedImpl = null;
430             if ( committedModule.getImplementations() != null )
431             {
432                 for ( final Iterator it = committedModule.getImplementations().
433                     getImplementation().iterator(); it.hasNext(); )
434                 {
435                     final ImplementationElement i =
436                         (ImplementationElement) it.next();
437 
438                     if ( i.getIdentifier().equals( impl.getIdentifier() ) )
439                     {
440                         committedImpl = i;
441                         break;
442                     }
443                 }
444             }
445 
446             if ( committedImpl != null )
447             {
448                 if ( committedImpl.getDependencies() != null )
449                 {
450                     for ( final Iterator it = committedImpl.getDependencies().
451                         getDependency().iterator(); it.hasNext(); )
452                     {
453                         final DependencyElement d =
454                             (DependencyElement) it.next();
455 
456                         try
457                         {
458                             final Dependency specifiedDependency =
459                                 impl.getDependencies().getDependency(
460                                 d.getName() );
461 
462                             final Class committedSpec = Class.forName(
463                                 d.getIdentifier(), true, Thread.currentThread().
464                                 getContextClassLoader() );
465 
466                             final Class specifiedSpec = Class.forName(
467                                 specifiedDependency.getSpecification().
468                                 getIdentifier(), true, Thread.currentThread().
469                                 getContextClassLoader() );
470 
471                             if ( !specifiedSpec.isAssignableFrom( committedSpec ) )
472                             {
473                                 this.getLog().error(
474                                     VerifyModelMojoBundle.getInstance().
475                                     getIllegalSpecificationMessage(
476                                     Locale.getDefault(), d.getName(),
477                                     impl.getIdentifier(),
478                                     specifiedDependency.getSpecification().
479                                     getIdentifier(), d.getIdentifier() ) );
480 
481                                 this.validModel = false;
482                             }
483                         }
484                         catch ( final MissingDependencyException e )
485                         {
486                             this.getLog().error(
487                                 VerifyModelMojoBundle.getInstance().
488                                 getMissingCommittedDependencyMessage(
489                                 Locale.getDefault(), d.getName(),
490                                 impl.getIdentifier() ) );
491 
492                             this.validModel = false;
493                         }
494                     }
495                 }
496 
497                 if ( committedImpl.getProperties() != null )
498                 {
499                     for ( final Iterator it = committedImpl.getProperties().
500                         getProperty().iterator(); it.hasNext(); )
501                     {
502                         final PropertyElement p = (PropertyElement) it.next();
503                         try
504                         {
505                             final Class committedType =
506                                 this.getModelManager().getJavaType( p );
507 
508                             final Property specifiedProperty =
509                                 impl.getProperties().getProperty( p.getName() );
510 
511                             if ( !specifiedProperty.getType().
512                                 isAssignableFrom( committedType ) )
513                             {
514                                 this.getLog().error(
515                                     VerifyModelMojoBundle.getInstance().
516                                     getIllegalPropertyMessage(
517                                     Locale.getDefault(), p.getName(),
518                                     impl.getIdentifier(),
519                                     specifiedProperty.getType().getName(),
520                                     committedType.getName() ) );
521 
522                                 this.validModel = false;
523                             }
524                         }
525                         catch ( final MissingPropertyException e )
526                         {
527                             this.getLog().error(
528                                 VerifyModelMojoBundle.getInstance().
529                                 getMissingCommittedPropertyMessage(
530                                 Locale.getDefault(), p.getName(),
531                                 impl.getIdentifier() ) );
532 
533                             this.validModel = false;
534                         }
535                     }
536                 }
537 
538                 if ( committedImpl.getMessages() != null )
539                 {
540                     for ( final Iterator it = committedImpl.getMessages().
541                         getMessage().iterator(); it.hasNext(); )
542                     {
543                         final MessageElement m = (MessageElement) it.next();
544                         try
545                         {
546                             impl.getMessages().getMessage( m.getName() );
547                         }
548                         catch ( final MissingMessageException e )
549                         {
550                             this.getLog().error(
551                                 VerifyModelMojoBundle.getInstance().
552                                 getMissingCommittedMessageMessage(
553                                 Locale.getDefault(), m.getName(),
554                                 impl.getIdentifier() ) );
555 
556                             this.validModel = false;
557                         }
558                     }
559                 }
560             }
561 
562             if ( committedModule.getSpecifications() != null )
563             {
564                 for ( final Iterator it = committedModule.getSpecifications().
565                     getSpecification().iterator(); it.hasNext(); )
566                 {
567                     final SpecificationElement s = (SpecificationElement) it.next();
568 
569                     try
570                     {
571                         final Specification spec = model.getModules().
572                             getSpecification( s.getIdentifier() );
573 
574                         final Multiplicity specifiedMultiplicity =
575                             this.getModelManager().getMultiplicity( spec );
576 
577                         if ( !this.getModelManager().getMultiplicity( spec ).
578                             equals( s.getMultiplicity() ) )
579                         {
580                             this.getLog().error( VerifyModelMojoBundle.getInstance().
581                                 getIllegalMultiplicityMessage(
582                                 Locale.getDefault(), s.getIdentifier(),
583                                 specifiedMultiplicity.getValue(),
584                                 s.getMultiplicity().getValue() ) );
585 
586                             this.validModel = false;
587                         }
588                     }
589                     catch ( final MissingSpecificationException e )
590                     {
591                         this.getLog().debug( e.getMessage() );
592                     }
593                 }
594             }
595         }
596         else
597         {
598             this.getLog().debug( VerifyModelMojoBundle.getInstance().
599                 getUncommittedMessage( Locale.getDefault(),
600                                        impl.getIdentifier() ) );
601 
602         }
603     }
604 
605     private void assertCompatibleModel( final Specification spec,
606                                         final Class clazz )
607         throws IOException, ClassNotFoundException, JAXBException
608     {
609         final SpecificationElement committedSpec =
610             this.extractSpecification( clazz );
611 
612         if ( committedSpec != null )
613         {
614             final Multiplicity specifiedMultiplicity =
615                 this.getModelManager().getMultiplicity( spec );
616 
617             if ( !specifiedMultiplicity.equals(
618                 committedSpec.getMultiplicity() ) )
619             {
620                 this.getLog().error( VerifyModelMojoBundle.getInstance().
621                     getIllegalMultiplicityMessage(
622                     Locale.getDefault(),
623                     committedSpec.getIdentifier(),
624                     specifiedMultiplicity.getValue(),
625                     committedSpec.getMultiplicity().getValue() ) );
626 
627                 this.validModel = false;
628             }
629         }
630         else
631         {
632             this.getLog().debug( VerifyModelMojoBundle.getInstance().
633                 getUncommittedMessage( Locale.getDefault(),
634                                        spec.getIdentifier() ) );
635 
636         }
637     }
638 
639     private ModuleElement extractImplementation( final Class clazz )
640         throws IOException, ClassNotFoundException, JAXBException
641     {
642         return (ModuleElement) this.extractModelObject(
643             clazz, Module.class.getName() );
644 
645     }
646 
647     private SpecificationElement extractSpecification( final Class clazz )
648         throws IOException, ClassNotFoundException, JAXBException
649     {
650         return (SpecificationElement) this.extractModelObject(
651             clazz, Specification.class.getName() );
652 
653     }
654 
655     private SpecificationsElement extractSpecifications( final Class clazz )
656         throws IOException, ClassNotFoundException, JAXBException
657     {
658         return (SpecificationsElement) this.extractModelObject(
659             clazz, Specifications.class.getName() );
660 
661     }
662 
663     private ModelObject extractModelObject( final Class clazz,
664                                             final String attributeName )
665         throws IOException, ClassNotFoundException, JAXBException
666     {
667         byte[] data = null;
668         ModelObject e = null;
669 
670         final String classLocation =
671             clazz.getName().replace( '.', '/' ) + ".class";
672 
673         final InputStream classFile =
674             Thread.currentThread().getContextClassLoader().
675             getResourceAsStream( classLocation );
676 
677         final ClassParser parser =
678             new ClassParser( classFile, classLocation );
679 
680         final JavaClass javaClass = parser.parse();
681         final Attribute[] attributes = javaClass.getAttributes();
682 
683         for ( int i = attributes.length - 1; i >= 0; i-- )
684         {
685             final Constant constant = javaClass.getConstantPool().
686                 getConstant( attributes[i].getNameIndex() );
687 
688             if ( constant instanceof ConstantUtf8 )
689             {
690                 if ( attributeName.equals(
691                     ( (ConstantUtf8) constant ).getBytes() ) )
692                 {
693                     if ( attributes[i] instanceof Unknown )
694                     {
695                         data = ( (Unknown) attributes[i] ).getBytes();
696                         break;
697                     }
698                 }
699             }
700         }
701 
702         if ( data != null )
703         {
704             e = (ModelObject) this.getModelManager().getContainerUnmarshaller().
705                 unmarshal( new GZIPInputStream( new ByteArrayInputStream(
706                 data ) ) );
707 
708             if ( this.getLog().isDebugEnabled() )
709             {
710                 final StringWriter writer = new StringWriter();
711                 this.getModelManager().getContainerMarshaller().
712                     marshal( e, writer );
713 
714                 this.getLog().debug( writer.toString() );
715             }
716         }
717 
718         return e;
719     }
720 
721     //---------------------------------------------------------VerifyModelMojo--
722 }