1 2 Previous Next 20 Replies Latest reply: Aug 28, 2010 1:51 AM by Andrew Rubinger RSS

DSL for XML descriptors

Dan Allen Master

I'd like to rekindle the discussion about the descriptors DSL (Re: ShrinkWrap - Descriptors). The descriptors DSL is a fluent API for producing standard XML-based configuration files such as:

 

  • web.xml
  • beans.xml
  • ejb-jar.xml
  • application.xml
  • faces-config.xml
  • persistence.xml

 

Of course, the design should be extensible so that non-standard descriptors can be introduced.

 

The discussion about creating these descriptors seems to have been abandoned, possibility because it's not linked to the ShrinkWrap space. An umbrella JIRA issue was never created, though there are JIRAs for each individual descriptor:

 

SHRINKWRAP-44 - web.xml

SHRINKWRAP-45 - application.xml

SHRINKWRAP-46 - ra.xml

SHRINKWRAP-115 - beans.xml

 

We are missing ejb-jar.xml and persistence.xml, and should probably add support for the JBoss AS-specific descriptors after we are done with the standard ones.

 

The other problem is that the pastebin links that Aslak posted seemed to have expired. Aslak, could you post the latest prototype code that you have? And preferably in a place it will not expire?

 

I consider this issue high priority for each of use of Arquillian.

  • 1. Re: DSL for XML descriptors
    Andrew Rubinger Master

    Absolutely agree with the feature as presented.  We've had this in our sights pretty much since the inception of ShrinkWrap, though I've been pretty firm to hold off until the core APIs and impls are finalized as stable and released.  Not to bite off more than we can chew, so to speak.

     

    That said, I'd be interested in seeing an API proposal for a limited subset of just one of the above spec DSLs.  We should likely start these in an extension module, maybe even outside the rest of the ShrinkWrap release cycle/process.

     

    And I'd even like to see modules separating each concern: for each spec type we'll have the fluent DSL API bit, a schema binding layer, and XML>Object>XML converters of some form.  Experience with the jboss-metadata project also shows that it's a good idea to have the API represented as interfaces and let some impl classes define the object model and associated XB annotations.

     

    To sum up, yep we need to go here.  Should be a priority.  But I'd like to conserve our resources first to the ShrinkWrap API freeze (ie. 1.0.0-beta-1), user guide and documentation, and 1.0.0.  From there I think porting in features like this make for excellent 1.1.0.

     

    With an increasing number of projects consuming ShrinkWrap now, out of consideration locked API has gotta come soon or we'll make everyone mad despite our current "alpha" badge.

     

    S,

    ALR

  • 2. Re: DSL for XML descriptors
    Andrew Rubinger Master

    Ah, another note.

     

    I'm not sure the DSL stuff should be a property of the archive.  For instance:

     

    webArchive.webXml().servletClass(MyServlet.class); 
    

     

    Because we already have methods to add files, resources (any Asset, really) as the web.xml.  I still view archives as "virtual filesystems which contain resources at paths".  I remember disagreeing with Aslak/Emmanuel about this some time back.

     

    Instead I like:

     

    webArchive.webXml( Descriptors.create(WebXml.class).servletClass(MyServlet.class) );
    

     

    ...where "Descriptors" itself returns an Asset type.  Or alternatively:

     

    webArchive.as(WebXml.class).servletClass(MyServlet.class); 

     

    ...I could dig that too.  So WebXml is itself Assignable.  But archives in my view do not have web properties.  They contain resources which have web properties.

     

    S,

    ALR

  • 3. Re: DSL for XML descriptors
    Dan Allen Master

    Andrew Rubinger wrote:

     

    I like:

     

    webArchive.webXml( Descriptors.create(WebXml.class).servletClass(MyServlet.class) );
    

     

    ...where "Descriptors" itself returns an Asset type.  Or alternatively:

     

    webArchive.as(WebXml.class).servletClass(MyServlet.class); 
    

     

    ...I could dig that too.  So WebXml is itself Assignable.  But archives in my view do not have web properties.  They contain resources which have web properties.

     

    I'm absolutely in agreement with you. I think we have two parallel builders. We have the ShrinkWrap class + family that build archives. Then we have Descriptors + family that build descriptors. When the descriptor is ready, it can be popped into the archive.

     

    I see an API emerging that strikes a balance between the two snippets above, in semantics and readability:

     

    webArchive.descriptor(WebXml.class).addServletClass(MyServlet.class, "/servletpath")
       .addContextParam("key", "value").addAsWebXml().addClass(...);
    

     

    I'm uncertain how to end the descriptor DSL and get back to the archive DSL. I proposed "addAsWebXml". Perhaps someone can chime in there.

     

    All I'm suggesting here is that the archive be aware of the general concept of descriptor building. Other than that, it has no direct tie to a descriptor (though perhaps there is a way to limit the types).

     

    public interface Archive<T extends Archive<T>> extends Assignable {
       ...
       <D extends Descriptor> D descriptor(Class<D> clazz);
    }
    
    public interface WebXml extends Descriptor<WebXml> {
       Descriptor<?> addServletClass(Class<? extends Servlet> clazz, String... urlPattern);
       Descriptor<?> addContextParam(String key, String value);
       ...
       // this will call setWebXml() on WebArchive and return the archive
       Archive<? extends WebArchive> addAsWebXml();
    }
    

     

    Don't hurt me, I'm a newbie with designing DSLs. Hopefully you can take this and run with it.

  • 4. Re: DSL for XML descriptors
    Dan Allen Master

    I do believe that an archive having a descriptor (in the general sense of the word) is a universal concept. Go as far out as epub (which would be another excellent use of ShrinkWrap). It has at least one descriptor that holds the metadata for an ebook reader. So I think it's justified that the Archive class know how to fork into descriptor building.

  • 5. Re: DSL for XML descriptors
    Andrew Rubinger Master

    Dan Allen wrote:

    I'm uncertain how to end the descriptor DSL and get back to the archive DSL. I proposed "addAsWebXml". Perhaps someone can chime in there.

    This is where I think the "Assignable" type comes in.  From there we can go:

     

    archive.as(WebXml.class).servlet(MyServlet.class,"/path").as(WebArchive.class).addResource("myfacelet.xml");

     

    Please excuse the poor DSL-ing.   Just showing how we can assign as WebXML (which would probably be some bridge object able to both add the resource and give access to the Descriptor) and back again out to the archive view.

     

    S,

    ALR

  • 6. Re: DSL for XML descriptors
    Andrew Rubinger Master

    Dan Allen wrote:

     

    I do believe that an archive having a descriptor (in the general sense of the word) is a universal concept. Go as far out as epub (which would be another excellent use of ShrinkWrap). It has at least one descriptor that holds the metadata for an ebook reader. So I think it's justified that the Archive class know how to fork into descriptor building.

    Here again I think the saviour is "Assignable".  It's basically the hook that gives us multiple interitence via wrapping an underlying archive, and gets specialized into any view.

     

    For instance, the exporters are Assignable, but not Archives or Container types.

     

    S,

    ALR

  • 7. Re: DSL for XML descriptors
    Aslak Knutsen Master

    My first attempt(the expired link) handled the descriptors using the Assignable method. Here is my little summary of the issue.

     

    • With the current extensions implementation you don't know what type of Archive you're being assigned from, you just get the pure Archive or a new wrapped Archive in another extension. This is not a issue with web.xml or ejb.xml but with beans.xml which can be in both a WebArchive and a JavaArchive. You don't know where to place the descriptor, /META-INF or WEB-INF ?
    • Using the Assignable method, it's not clear what happens when you do: archive.as(Descriptor).something().as(JavaArchive).setDescriptor(Asset) ?
    • A Descriptor should have access to the Archive so it can add the classes along side the descriptor definition of it, so you don't have to do: archive.addClass(servlet).addWebXmlServlet(servlet)
    • The descriptor should be able to know where to add it self. We should try to avoid add(Descriptor, "/WEB-INF/web.xml").
  • 8. Re: DSL for XML descriptors
    Dan Allen Master

    I'm working on a formal prototype in the shrinkwrap-sandbox project on gitbub (by formal, I mean more formal than using pastebin, so people can actually compile this stuff).

     

    I'm using JAXB to generate the XML. So far, I've implemented the persistence and beans descriptors as well as a fluent API to create them. I'll merge in Aslak's web descriptor patch soon.

     

    I've made the descriptor a ShrinkWrap Asset, rather than an Archive, which makes a heck of a lot more sense to me. Here's what it will look like:

     

    PersistenceDef persistence = Descriptors.create(PersistenceDef.class)
          .persistenceUnit("test").transactionType(TransactionType.JTA)
                .provider(ProviderType.HIBERNATE)
                .jtaDataSource("java:/DefaultDS")
                .classes(User.class)
                .schemaGenerationMode(SchemaGenerationModeType.CREATE_DROP)
                .showSql()
                .formatSql()
                .property("hibernate.transaction.flush_before_completion", true)
          .persistenceUnit().name("another").transactionType(TransactionType.RESOURCE_LOCAL)
                .provider(ProviderType.ECLIPSE_LINK)
                .nonJtaDataSource("jdbc/__default")
                .excludeUnlistedClasses()
                .schemaGenerationMode(SchemaGenerationModeType.CREATE);
          
    BeansDef beans = Descriptors.create(BeansDef.class)
          .interceptor(InterceptorSample.class)
          .interceptor(AnotherInterceptorExample.class)
          .decorator(DecoratorSample.class)
          .alternativeStereotype(StereotypeExample.class);
          
    ShrinkWrap.create("test.jar", JavaArchive.class)
            .addManifestResource(persistence, "persistence.xml")
            .addManifestResource(beans, "beans.xml");
            .as(ZipExporter.class).exportZip(new File("/tmp/test.jar"), true);
    

     

    Feel free to hackup the github project. This is by no means set in stone. What's important is that we have a place to collaborate on the API.

     

    I consider this a very important issue. As people are beginning to use Arquillian more regularly, it's a huge hole to not be able to create the descriptors. Writing XML strings in Java is horrible and having to switch over to an XML file breaks the developer's flow in the test.

     

    Plus, having this functionality will be a huge boast for the project. People are going to flip when they see it.

  • 9. Re: DSL for XML descriptors
    Andrew Rubinger Master

    Dan Allen wrote:

     

    I've made the descriptor a ShrinkWrap Asset, rather than an Archive, which makes a heck of a lot more sense to me. Here's what it will look like:

    Fantastic.

     

    S,

    ALR

  • 10. Re: DSL for XML descriptors
    Aslak Knutsen Master

    Very nice!

     

    It needs to solve 2 more things then it's perfect:

     

    • You should only have to add Classes to the Descriptor and they should be added to the Archive as well
    • You should be able to let the Descriptor pick it's storage location and name by default

     

    A possible solution could be to have a common Descriptors interface and add  add(Descriptor) to the Archive interface. On add(Descriptor) the Archive could give the Descriptor a callback to whom it was added to.

     

     

    public class ArchiveImpl implements Archive
    {
         public T add(Descriptor desc)
         {
              desc.populate(this);
         }
    }
    
    public class BeanXml implements Descriptor 
    {
         public void populate(Archive<?> archive)
         {
              if(WebArchive.class.isInstance(archive)) 
              {
                   WeldArchive.cast(archive).addWebResource(makeAsset(), name);
              }
              else if(JavaArchive.class.isInstance(archive)) 
              {
                   JavaArchive.cast(archive).addManifestResource(makeAsset(), name);
              } 
              else 
              {
                   throw new Exception("Unsupported Archive: " + archive);
              }
              addClasses(ClassContainer.cast(archive));
         }
         
         private Asset makeAsset() {}
         
         private ArchivePath name() { return "beans.xml" }
         
         private void addClasses(ClassContainer container) 
         {
              contianer.addClasses(interceptors);
              contianer.addClasses(alternatives);
              ...
         }
    }
    

     

    BeanXml is more advanced then most descriptors since it can support two different Archive types..

     

    If we want to avoid the Type checking and casting etc in the Descriptor, we could add some magic to the add(Descriptor) impl:

     

    public class ArchiveImpl implements Archive
    {
         public T add(Descriptor desc)
         {
              // find the populate method matching this archive..
              Method method = desc.getClass().getMethods();
              
              method.invoke(desc, this);
         }
         
         
    }
    
    public class BeanXml implements Descriptor 
    {
         public void populate(WebArchive archive)
         {
              archive.addWebResource(makeAsset(), name);
              addClasses(archive);
         }
         public void populate(JavaArchive archive)
         {
              archive.addManifestResource(makeAsset(), name);
              addClasses(archive);
         }
         
         private Asset makeAsset() {}
         
         private ArchivePath name() { return "beans.xml" }
         
         private void addClasses(ClassContainer container) 
         {
              contianer.addClasses(interceptors);
              contianer.addClasses(alternatives);
              ...
         }
    }
    

     

     

    That will give us the following..

     

    ShrinkWrap.create(JavaArchive.class)
         .add(
              Descriptors.create(BeanXml.class)
                   .enableInterceptor(Transactions.class)
         )
         .addClass(BankAccount.class);
    

     

    Nifty!

     

     

  • 11. Re: DSL for XML descriptors
    Aslak Knutsen Master

    and one more thing..

     

    • A Descriptor should keep in sync with changes to the archive, add / remove etc

     

     

    public class ApplicationXml extends Descriptor 
    {
         public void populate(EnterpriseArchive archive)
         {
              archive.setApplicationXml(makeAsset());
              archive.registerListener(
                   new UpdateApplicationXmlOnModuleChange();
              );
         }
    }
    
    ShrinkWrap.create(EnterpriseArchive.class)
         .add(
              Descriptors.create(ApplicationXml.class)
                   .something()
         )
         .addModule(war);
    

     

    The UpdateApplicationXmlOnModuleChange should then be able to pick up on the addModule call and register it with it's descriptor metadata.

     

    ?

     

  • 12. Re: DSL for XML descriptors
    Andrew Rubinger Master

    It occurs to me that we can achieve some greater separation.

     

    For instance:

     

    http://github.com/ALRubinger/shrinkwrap-sandbox/blob/master/shrinkwrap-descriptors/src/main/java/org/jboss/shrinkwrap/descriptor/spec/persistence/PersistenceDef.java

     

    Dan's prototype here works fantastically as a standalone API without any ShrinkWrap references.  We could actually spin this whole thing off into another project, which aims to generate spec DSLs using a method-chaining (ie. fluent) API.  In other words, PersistenceDef wouldn't implement Asset.


    Then some ShrinkWrap extension can come along and provide the integration necessary: adapt this into an Asset, put it in the default spec locations in the Archive (as Aslak mentions), listen on archive events and change the descriptor appropriately, etc.

     

    S,

    ALR

  • 13. Re: DSL for XML descriptors
    Aslak Knutsen Master

    DefWrap ?

  • 14. Re: DSL for XML descriptors
    Dan Allen Master

    I agree with both of you. Having to pick the location by default seems redundant. Also, I totally agree that this could be a reusable library! Implementing the Asset interface was just a convenience on the first go around, but it would be easy enough to push this into an extension class.

     

    Just to prove how generally useful this can be. Imagine for a minute that you want to develop a web application, but define your web.xml entirely in Java (the same goes for other descriptors). You simply implement a factory, not unlike Arquillian's @Deployment method.

     

    public class WebAppFactory
    {
       @Descriptor("${project.build.directory}/${project.build.finalName}/WEB-INF/web.xml")
       public Asset create()
       {
          return Descriptors.create(WebAppDef.class)
                .displayName("My Webapp")
                .facesDevelopmentMode()
                .servlet("javax.faces.webapp.FacesServlet", new String[] { "*.jsf" })
                .sessionTimeout(3600);
       }
    }
    
    

     

    Then you use a Maven plugin to generate it into your output directory at the end of the compile phase (so it gets packaged or hot deployed as needed).

     

     

    <plugin>
       <groupId>org.jboss.shrinkwrap</groupId>
       <artifactId>maven-descriptor-plugin</artifactId>
       <version>1.0.0-SNAPSHOT</version>
       <executions>
          <execution>
             <configuration>
                <!-- Remove configuration once annotation processing is implemented -->
                <factoryClass>com.acme.WebAppFactory</factoryClass>
                <target>${project.build.directory}/${project.build.finalName}/WEB-INF/web.xml</target>
             </configuration>
             <goals>  
                <goal>generate</goal>
             </goals> 
          </execution>
       </executions>
    </plugin>
    

     

     

    I've hacked up a very crude Maven plugin to implement this idea in the ShrinkWrap github sandbox.

     

    maven-descriptor-plugin master tree

     

    Image the possibilities.

     

    This would be really nice for CDI in that you could active interceptors, alternatives, etc in Java code. Of course, it would be easy to tie factory methods into Maven profiles so that different descriptors are generated in different deployment scenarios.

1 2 Previous Next