logo       

Re: Exemplar configuration thoughts: msg#00129

java.hudson.user

Subject: Re: Exemplar configuration thoughts


I wonder if the semantics you are suggested could be easily achieved today by:

1. create a project whose sole purpose is to be an exemplar
2. create 'real' projects by copying it
3. customize configurations of individual projects

There are obvious downsides to this, like once created the project will not reflect any changes made in the exemplar, but I think it applies to your suggestion below as well.

Or am I missing your point?

Eric Crahen wrote:
I have a lot clearer picture of what I think might be needed to reduce the
amount of per-project configuration required when many projects would use
the same plugin with the same or almost identical configuration. I've been
doing some experimentation and I've found a way to do what I'd like to do,
but I think we can make some big improvements here. Some of it some of what
you've said earlier. I thought it might be better all collected in an
appropriately named post since I've come to this all in a roundabout way.

First, I'll just briefly mention something about the global configuration.
Terminology sometimes differes between people and is the source for a lot of
confusion. The notion of global configuration we have right now is that its
for settings that are orthogonal to the actual settings a Project would be
interested in. For example,

- The Ant task stores a global configuration in its Descriptor singleton.
The values it stores are just the names and locations of the Ant
installations available on the system. This configuration literally consists
of names and paths. The Project configuration for the Ant task is stored in
the instances of the Ant Builder member variables. The configuration for
this component is not paths and names, its a reference to to the AntLocation
to use.

In this example, the global configuration is a definition, the project
configuration values are a selection. Related, but orthogonal. This works
very well for the sort of thing that Ant configures.

The notion of global configuration I have been looking for is that of an
exemplar global.

- There is a global instance of an ExtensionPoint and configure it as an
exemplar. The configuration is actually stored in an ExtensionPoint
instance, and not in the Descriptor.
- My global.jelly and config.jelly would look very similar, in fact they
could be identical - the only difference is the instance of the
ExtensionPoint being configured.

This might seem similar because the word global keeps popping up (but thats
what this clarification is about) The subtle difference here is that, the
global configuration in this case is not orthogonal to the project
configuration. It actually is the same thing.

---

Now here are the challenges that I've been facing trying to come up with
something.

The first challenge is where does the global exemplar configuration go? On
the global configuration page, there is no ${instance}. There is only the
descriptor. So what options are there?

#1 Create fields on the Descriptor to store these defaults.
#2 Store a reference to my global exemplar within the Descriptor singleton.

Of these two options, I like the second better. I don't have to duplicate
fields in another class to simulate an exemplar. I actually just use an
instance of the thing I'm making an example of - which makes sense. The
drawback here is really more structural, Descriptors are usually nested. So
here my nested singleton has a reference to a shared instance of its outer
class. Its a little funny - but this is only an aesthetic issue.

Lets say I pick #2 for the sake of argument. I have a place to put my
exemplar config now. The next challenge is that I need to update the correct
object.

#1 Make the global.jelly use ${descriptor.exemplar.value} and the
config.jelly use ${instance.value}.
#2 Use identical jelly's (jellies?) and differentiate in
configure(StaplerRequest) which instance I should bindParameters() to based
on the URI.
#3 Use identical jelly's and do something in the XML to set a reference to
the instance to configure. If there is no ${instance} then use ${
descriptor.exemplar}

The first option is somewhat tedious for any non-trivial configuration. I
have to write the same verbose jelly file twice, I have to change two
places.

The second option allows me to use the same jelly file (in theory at least,
I actually have to use copies since the stapler include tag here looks for
included files under hudson/model/AbstractProject). I have to write less XML
which is a giant plus, but it seems a little hacky to examine the URI and if
starts with /hudson/job select one object other wise select another. When
the URI changes, I'll have to update the code.

The third option is making me do some programming in XML which isn't great,
but its only 1 if statement so maybe this isn't too bad.

Let's not pick one of these yet, the next challenge influences which one I
can use right now.

Finally, the last challenge is that when you first create a Job, and that
project configuration page appears for the first time, you'll have a bunch
of fields on the screen - but you still won't get an ${instance} until the
first time you submit this form.

This is probably the biggest challenge because when this page is drawn for
the first time, what I really want to do is have the ability to initialize a
new ExtensionPoint using the global exemplar. I *really* want to do this in
Java, not jelly.

The only way to work around this at the moment is to put logic into jelly
for each field which would be this psuedo-code:

if(${instance} == null)
${descriptor.exemplar.field}
else
${instance.field}

In this way, the page will be correctly rendered on its first display, and
when the form is submitted we'd be able to bindParameters() to an instance
of the ExensionPoint. On the next display, ${instance} won't be null and
we'd be using it normally.

----

Ideas for things that could be improved, I'll list them in order of
importance or difficulty.

* The first thing is I don't want to have this much logic in Jelly. This is
really something that belongs in a factory since its very much tied to
properly instantiating an ExensionPoint.

One possibility would be to pass the StaplerRequest that requests the
first load of a project config page into the Descriptor.newInstance() method
to give ExentsionPoints a chance to initialize themselves (using the global
exemplar if needed). The effect being that ${instance} in a
config.jellypage would not be null when the page is first loaded and
the user had a
chance to initialize.

Then you need a place for it. One place that seems logical is the
Descriptor, getExemplarInstance() or something like that maybe?

* Descriptors must be singletons w/o exception,

In AbstractProject, addToList() and removeFromList() use == to add and
remove extension points from a Project, so you actually will run into
difficulty if anything but a singleton Descriptor is used. One way to break
it is implement per-ExtensionPoint Descriptors. You'll find you'll never
save your projects settings.

This could be replaced with a Comparator that uses getClass() to perform
the comparision test and have better results.

* There is an odd circular reference between ExtensionPoints and their
factories, getDescriptor()

I've looked at this only because in some of what I expiremented with I
really began to wonder why I'd getDescriptor() was implemented as it was. I
tried to implement getDescriptor() in such a way that each ExtensionPoint
instance created a new instance of its Descriptor. This, of course, fails
because you wind up losing project setting you configure (due to the == test
in AbstractProject I think).

I think making the change suggested about would allow for getDescriptor() to
not have to be a singleton - but then I have to be careful about storing and
global settings (Ant task style global settings) in static variables. What
really bother me about getDescriptor() is that its easy to implement wrong,
and its hard to confirm you implemented it right.

It doesn't look simple to remove getDescriptor(), it seems mainly to be used
from within jelly in a few places, and I don't completely understand that
code - so I can't make a rational suggestion about what would be better at
this time. Maybe this is something you have though of too?

The benefit to there not being a getDescriptor() would be that you just
create one, register it once when the plugin starts and you're done. There
wouldn't been any fuzzy gotchas.



--
Kohsuke Kawaguchi
Sun Microsystems kohsuke.kawaguchi@xxxxxxx

Attachment: smime.p7s
Description: S/MIME Cryptographic Signature

<Prev in Thread] Current Thread [Next in Thread>
Google Custom Search

News | FAQ | advertise