Skip to content

Instantly share code, notes, and snippets.

@ralfbattenfeld
Created June 15, 2011 21:50
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save ralfbattenfeld/1028220 to your computer and use it in GitHub Desktop.
Discussion of two API flavors for the Shrinkwrap Descriptor project
Settings filters in a webapp descriptor
---------------------------------------
First Option using up() for jumping back to the parent node: Second option using type information
String desc = create(WebApp30Descriptor.class) String desc = create(WebAppDescriptor.class)
.filter() .filter(type(Filter.class)
.setFilterClass("org.dot.clazz") .name("name")
.setFilterName("name").up() .className("org.dot.clazz") )
.filterMapping() .filterMapping(type(FilterMapping.class)
.setFilterName("name") .filterName("name")
.setUrlPattern("mapping").up() .urlPattern("http://someLink") )
.exportAsString(); .exportAsString();
@tommysdk
Copy link

I would appreciate the second approach over the first one. With the second option I would feel like I don't need to think about the document hierarchy structure. I think it's nicer to read too.

@ALRubinger
Copy link

@ALRubinger
Copy link

My opinion is for 1). I think it's easier to write, because:

"create(WebAppDescriptor.class).filter( type(Filter.class).name("name") )"

...here we've defined that we're going to enter the "filter" element, yet to get the right type information the burden is on the user to then construct a new Type of Filter, and pass it in? Contrasted just with:

"create(WebAppDescriptor.class).filter().name("name").up();"

...here "filter()" brings you to the right level and the proper type without it needing to be defined again by the user.

The 2nd approach uses also wrapping/nesting to define which "level" you're in, which IMO breaks fluency.

@ALRubinger
Copy link

BTW, I think Option 2 is easier to read. But Option 1 is easier to write, which I find more important.

@aslakknutsen
Copy link

The example above is not 100% correct according to the original idea,

it should have been .add(type(Filter.class)) & .add(type(FilterMapping.class))

@ALRubinger
Copy link

ALRubinger commented Jun 16, 2011 via email

@ralfbattenfeld
Copy link
Author

Hi
I think, as a contributor of this API, I should not prioritize or influence the discussion... I hope, many comments are coming in.

@ALRubinger
Copy link

Ralf: Absolutely you should!

@ralfbattenfeld
Copy link
Author

Personally, I think it should be similar to the way a user defines the content of the descriptors via text or xml editor. So that the user can easily relate it to what he is familiar with. Under that perspective, I prefer a little bit option 1.

@tommysdk
Copy link

The first option may be easier to write, but I'm not that keen on the up()-approach. Sure it may be familiar to users to think in terms of XML, but I don't think that the API should expose all the underlying details, e.g. what the hierarchy of the XML document looks like. I don't think the API should just be a plain Java representation of XML. Also as a developer I feel that the second approach is more natural to write. Maybe not easier but at least more natural. But I guess that is a mindset thing.

@ALRubinger
Copy link

Tommy: Interesting ideas. But ShrinkWrap Descriptors is defined as an object representation of metadata. In the case of EE metadata, that form is hierarchical: XML. So then doesn't it make sense to model that as accurately as possible?

@tommysdk
Copy link

Have to admit that I haven't looked deeper into the descriptors project yet. But it would make sense indeed. Just thinking that the objects themselves could still be accurate object representations of the metadata even if the actual DSL doesn't look like that, couldn't they? I'm also thinking that you shouldn't have to be all that familiar with XML to be able to understand the API (even though you should be to use it).

@stuartwdouglas
Copy link

I don't like the way you have to specify the type twice in the second one:

.filterMapping(type(FilterMapping.class)

Or is there something I am missing here?

@ALRubinger
Copy link

ALRubinger commented Jun 17, 2011 via email

@aslakknutsen
Copy link

Option 2 - Details

Basic Types

  • Descriptor

The 'mother' interface for the Top Level Node.

  • Type<R extends TypeReader, B extends TypeBuilder>

All 'elements' are of a Type<R, B>, a generic contruct of something that has associated a Read-Only-View (TypeReader) and a Builder view(TypeBuilder). Type being the Write view will in most cases also extend the Read-Only-View(TypeReader)

  • TypeReader

This is the Read-Only-View of a Type. Used in e.g. get operations so the Types state can be passed on without having to worry about it changing.

  • TypeBuilder

A Builder is a way to create a Type. This would be a seperated 'factory' interface if you will, and can contain convinience methods for creating a specific type.

  • SubType<R, S extends Type>

This describs that a Type can have SubTypes, e.g. WebAppDescriptor.add(WebAppType)

  • SubTypeReader<X extends TypeReader, S extends Type<X, ?>>

The Read-Only-View of the SubTypes, e.g. WebAppDescriptor.get(WebAppType)

The Type and TypeReader are generated from the xsd source, while the TypeBuilder is left for manual impl on a pr type basis. Not all Types will have any logical build helpers.

Usage

So, a specific Descriptor impl would look something like this:

public interface WebAppDescriptor extends 
    Descriptor, 
    SubType<WebAppDescriptor, WebAppType<? extends TypeReader, ? extends TypeBuilder>>, 
    SubTypeReader<WebAppTypeReader, WebAppType<WebAppTypeReader, WebAppTypeBuilder>>
{
}

public interface WebAppTypeReader extends TypeReader {}

public interface WebAppTypeBuilder extends TypeBuilder {}

public interface WebAppType<READER extends WebAppTypeReader, BUILDER extends WebAppTypeBuilder> extends     
    Type<READER, BUILDER>
{
}

And for a SubType of WebApp, e.g. Servlet it could look like this:

// Contains Helper methods for Creating a Sevlet Type.
public interface ServletBuilder extends WebAppTypeBuilder
{
  Servlet from(Class<? extends Servlet> servlet);
}

// Read-Only-View of the Servlet Type
public interface ServletReader extends WebAppTypeReader
{
  String name();

  String className();
}

// Master Servlet Type, binds it all togather and contains the Write view.
public interface Servlet extends WebAppType<ServletReader, ServletBuilder>, ServletReader 
{
  Servlet name(String name);

  Servlet className(String className);
}

Now we have a clear seperation between read and write operations, and also a clear seperation between 'pure' generated model and the convenience/helper view for creating the Type.

When we want to create a the complete model manually, we can do the following:

// Using the type() method will give us the complete view of a Type
WebAppDescriptor desc = create(WebAppDescriptor.class)
 .add(type(Servlet.class)
        .className("com.acme.MyServlet") // set the data
        .name("MyServletName")); // set the data

And when we want to Read it, or pass it on to someone else, we can use the Read-Only-View:

WebAppDescriptor desc = create(WebAppDescriptor.class)
 .add(type(Servlet.class)
        .className("com.acme.MyServlet")
        .name("MyServletName"))

// Using the get() operation from SubTypeReader we get the Read-Only-View
Collection<ServletReader> servlets = desc.get(Servlet.class);
for(ServletReader servlet : servlets)
{
    System.out.println(servlet.className());
    //System.out.println(servlet.className("")); <-- does not exist on the ServletReader
}

We have multiple convenience methods for creating a Servlet Type, e.g. we can extract name and className directly from a Class<? extends Servlet>

// by using the build() method we get access to the TypeBuilder view
WebAppDescriptor desc = create(WebAppDescriptor.class)
 .add(build(Servlet.class)
         .from(MyServlet.class));

See https://gist.github.com/1025597#file_web_app_descriptor_proto_with_builder.java for complete source..

ps. I'm having some issues with the Generics that I hvaen't quite figured out how is suppose to work yet, but..

@ralfbattenfeld
Copy link
Author

Impressive proposal!

My concern here is that some types are deriving from the root node, e.g. the descriptor. This is fine as long as the subtype is not used somewhere else. For example, the servlet type is defined in the web-common schema, used in the webapp and webfragment descriptor. How do we model this in your approach? I think, it is important to respect the re-usable nature of the schema definition.

@aslakknutsen
Copy link

A Type that has a SubType, define a common Type as its Sub.

In the example above, a WebAppDescriptor can take any Type that extends WebAppType as its SubType.

Being this will be generated from the XSDs, let's do a quick review base on those names:

WebAppDescriptor
  .add(WebCommonType)

WebFragmentDescriptor
  .add(WebCommonType)

Servlet extends WebCommonType
ServletMapping extends WebCommonType
Filter extends WebCommonType
FilterMapping extends WebCommonType
SessionConfig extends WebCommonType
Listener extends WebCommonType

@ralfbattenfeld
Copy link
Author

Thanks Aslak for the clarification. I am sitting right now in a boring classroom:-(

When I look the second time to your proposal, I have the feeling that this approach is complex.
I prefer simpler solutions, if possible.

You mentioned that some some classes are created manually. Can you count how many classes are manually created? I truly believe that the majority of the classes (api and impl) must be generated. Otherwise this will be a never ending project.

@aslakknutsen
Copy link

The generation is a one time thing right, so the generation will generate the TypeBuilder's as well, but possible as just empty interfaces.

I think it will be hard to generate the convenience methods for the TypeBuilders, since that is very much up to the type and what is possible based on what other things we have around and information not available in the XSD. So a Type's TypeBuilder can be manually extended as we see fit, but only as helpers for creating a Type.

So to explain from the Example:

// ServletBuilder interface is generated from the XSD as part of the Servlet Type
public interface ServletBuilder extends WebAppTypeBuilder
{
  // The convenience method from() is added manually after generation
  Servlet from(Class<? extends Servlet> servlet);
}

That should give us a fully generated core Model based on the XSD, but with a opening to do manual adjustments to the Type creation.

@ralfbattenfeld
Copy link
Author

Hi All

I am after the education trip in the nice Caribbean for two weeks. Sun, beach, everything is beautiful. But I wanted to say that I have my laptop with me and a will proceed with more testing work. I am thinking of a generated test cases to cover more of the functionalities. This is independent of the API discussion. In the meanwhile, the transformation is now integrated into maven. I had to choose a better maven xml plugin.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment