You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@sling.apache.org by Jason Bailey <Ja...@sas.com> on 2014/08/28 20:49:56 UTC
Some thoughts on Sling Models and injectors
Our team has been working on adopting the sling model paradigm which has given me a chance to get involved with creating a number of injectors to meet our needs.
I wanted to share some of my observations.
To clarify the existing flow, when we are looking at performing an injection it is really a three step process.
* Obtain the base Object from the supplied adaptable
* Retrieve a value from the base Object
* Manipulate the value to meet the type expectations of the annotated field
During any one of these steps, the result of the step could equal null and the process would then move on to the next injector.
While working on these injectors I came across a series of commonalities within their implementations. You could actually define a shorthand for the majority of the injectors, and extrapolating from that you could define an injector completely in annotations.
Example annotations derived from existing
For defining the base object
@adaptable(Class<?>) The expected object to be used as the source of this injector
@via(String) use a JavaBean property of the adaptable as the source of the injection
@viaArgument(Object*) if present, use this as the argument for the @via access
@adaptTo(Class<?>) if present attempts to adapt the current base Object
For retrieving the value
@method(String) name of the method to use.
@named(String) name of the argument for the above @method, defaults to the property name
@default(Object*) replaces the returned value if null.
Which, if you put the logic for these annotations in the framework, would allow you to do things like this mockup of a RequestAttribute injector:
public class RequestAttributeInjector {
@adaptable(ServletRequest.class) @method("getAttribute")
Object getValue(Object adaptable, String name, Type declaredType, AnnotatedElement element, DisposalCallbackRegistry callbackRegistry){
return adaptable;
}
}
where a SlingBindings injector would look like this
public class SlingBindingsInjector {
@adaptable(ServletRequest.class) @via("attribute")
@viaArgument(SlingBindings.class.name()) @method("get")
Object getValue(Object adaptable, String name, Type declaredType, AnnotatedElement element, DisposalCallbackRegistry callbackRegistry){
return adaptable;
}
}
You'll notice I didn't touch step 3. Because once you extract these concepts into separate steps, type conversion is the same for all objects and therefore something that should be supplied by the framework. This sort of separation would provide consistency of type conversion across different injectors.
My TL;DR comes down to
I believe that by adding additional meta information to the sling injector definition, either through annotations or properties, that you could streamline the functionality and make it easier to develop new injectors. An example being the declaration of what type of adaptable is expected and verifying that prior to calling the injector.
The addition of the meta information could be added expanded to the point that most injectors could be defined as a series of properties.
Additionally, unifying the type conversion outside of the injector would be beneficial, as it provides a consistent type conversion process and enables improvements of that process to be shared by all injectors.
WDYT?
-Jason
RE: Some thoughts on Sling Models and injectors
Posted by Jason Bailey <Ja...@sas.com>.
I think I see where you are coming from with the "magic" comment. I tend to be slow sometimes writing these things out and they end up going through several iterations in my head.
I had gotten to the point where I saw the "getValue" as the equivalent of a post retrieval callback. With the idea that the return value had been obtained and that certain implementations would require that you do something above and beyond before being submitted to the type conversion.
So taking a step back, You can define any of the existing non-class based injectors as a series of very discrete steps that are separated into two groups. the first being the definition of the object to retrieve a value from and then the second are the definitions needed to retrieve the value.
So working on the assumption of a builder pattern which I assume your looking at to isolate this from what you are doing at the factory level. I could incorporate the callbacks like so;
return builder.withName("request-attributes").requiresAdabtable(ServletRequest.class).withMethod("getAttribute").setCallbacks(new InjectorBuilder.Callback(){
public Object postObject(Object adaptable) {
//do something funky
}
public Object postValue(Object adaptable, String name, Type declaredType, AnnotatedElement element, DisposalCallbackRegistry callbackRegistry) {
//do something funky
}
}).build();
So is this more preferable?
-Jason
________________________________________
From: justinedelson@gmail.com <ju...@gmail.com> on behalf of Justin Edelson <ju...@justinedelson.com>
Sent: Friday, August 29, 2014 11:44 AM
To: dev@sling.apache.org
Subject: Re: Some thoughts on Sling Models and injectors
Hi Jason,
I'm all for anything that could make injector development simpler. But
personally, that looks like a weird usage for annotations. Have you
considered using the Builder pattern?
Maybe an interface like this:
public interface InjectorFactory {
Injector build(InjectorBuilder builder);
}
And then you would have:
@Component @Service
public class RequestAttributeInjectorFactory {
public Injector build(InjectorBuilder builder) {
return builder.withName("request-attributes").withAdaptable(ServletRequest.class).withMethod("getAttribute").build();
}
}
This, IMHO, accomplishes the same goal, but somehow looks less
"magical" :) WDYT?
As I wrote yesterday in a separate thread, I do think there's been a
bit of sloppiness on my part about type checking/conversion. Unless
specifically required (i.e. the case of the ValueMapInjector), all
type checking and conversion should happen in the ModelAdapterFactory.
And really all conversion should happen via adaptTo().
Justin
On Thu, Aug 28, 2014 at 2:49 PM, Jason Bailey <Ja...@sas.com> wrote:
> Our team has been working on adopting the sling model paradigm which has given me a chance to get involved with creating a number of injectors to meet our needs.
>
> I wanted to share some of my observations.
> To clarify the existing flow, when we are looking at performing an injection it is really a three step process.
>
> * Obtain the base Object from the supplied adaptable
> * Retrieve a value from the base Object
> * Manipulate the value to meet the type expectations of the annotated field
>
> During any one of these steps, the result of the step could equal null and the process would then move on to the next injector.
> While working on these injectors I came across a series of commonalities within their implementations. You could actually define a shorthand for the majority of the injectors, and extrapolating from that you could define an injector completely in annotations.
>
> Example annotations derived from existing
>
> For defining the base object
> @adaptable(Class<?>) The expected object to be used as the source of this injector
> @via(String) use a JavaBean property of the adaptable as the source of the injection
> @viaArgument(Object*) if present, use this as the argument for the @via access
> @adaptTo(Class<?>) if present attempts to adapt the current base Object
>
> For retrieving the value
> @method(String) name of the method to use.
> @named(String) name of the argument for the above @method, defaults to the property name
> @default(Object*) replaces the returned value if null.
>
> Which, if you put the logic for these annotations in the framework, would allow you to do things like this mockup of a RequestAttribute injector:
>
> public class RequestAttributeInjector {
>
> @adaptable(ServletRequest.class) @method("getAttribute")
> Object getValue(Object adaptable, String name, Type declaredType, AnnotatedElement element, DisposalCallbackRegistry callbackRegistry){
> return adaptable;
> }
> }
>
> where a SlingBindings injector would look like this
>
> public class SlingBindingsInjector {
>
> @adaptable(ServletRequest.class) @via("attribute")
> @viaArgument(SlingBindings.class.name()) @method("get")
> Object getValue(Object adaptable, String name, Type declaredType, AnnotatedElement element, DisposalCallbackRegistry callbackRegistry){
> return adaptable;
> }
>
> }
>
> You'll notice I didn't touch step 3. Because once you extract these concepts into separate steps, type conversion is the same for all objects and therefore something that should be supplied by the framework. This sort of separation would provide consistency of type conversion across different injectors.
>
> My TL;DR comes down to
> I believe that by adding additional meta information to the sling injector definition, either through annotations or properties, that you could streamline the functionality and make it easier to develop new injectors. An example being the declaration of what type of adaptable is expected and verifying that prior to calling the injector.
>
> The addition of the meta information could be added expanded to the point that most injectors could be defined as a series of properties.
>
> Additionally, unifying the type conversion outside of the injector would be beneficial, as it provides a consistent type conversion process and enables improvements of that process to be shared by all injectors.
>
> WDYT?
>
> -Jason
RE: Some thoughts on Sling Models and injectors
Posted by Jason Bailey <Ja...@sas.com>.
Justin,
I hadn't considered the builder pattern for a number of reasons. Primarily I didn't view it as much of a construction of something, as more of a set of conditions that would need to be applied. Much like the annotations that are being used to define the injection of a value in a Model. In the example I mocked
> @adaptable(ServletRequest.class) @via("attribute")
> @viaArgument(SlingBindings.class.name()) @method("get")
> Object getValue(Object adaptable, String name, Type declaredType, AnnotatedElement element, DisposalCallbackRegistry callbackRegistry){
> return adaptable;
> }
This is setting conditions on the adaptable object being passed into the method. With the default, or lack of conditions resulting in the passing in of the adaptable as is, just like it is now.
When I listed out the annotations, the re-use of the ones in the models were deliberate. So that for a particular condition defined in the Injector, you could potentially override it from the model. I saw them mentally as the same thing, as instructions to the ModelAdapterFactory.
-Jason
-----Original Message-----
From: justinedelson@gmail.com [mailto:justinedelson@gmail.com] On Behalf Of Justin Edelson
Sent: Friday, August 29, 2014 11:45 AM
To: dev@sling.apache.org
Subject: Re: Some thoughts on Sling Models and injectors
Hi Jason,
I'm all for anything that could make injector development simpler. But personally, that looks like a weird usage for annotations. Have you considered using the Builder pattern?
Maybe an interface like this:
public interface InjectorFactory {
Injector build(InjectorBuilder builder); }
And then you would have:
@Component @Service
public class RequestAttributeInjectorFactory {
public Injector build(InjectorBuilder builder) {
return builder.withName("request-attributes").withAdaptable(ServletRequest.class).withMethod("getAttribute").build();
}
}
This, IMHO, accomplishes the same goal, but somehow looks less "magical" :) WDYT?
As I wrote yesterday in a separate thread, I do think there's been a bit of sloppiness on my part about type checking/conversion. Unless specifically required (i.e. the case of the ValueMapInjector), all type checking and conversion should happen in the ModelAdapterFactory.
And really all conversion should happen via adaptTo().
Justin
On Thu, Aug 28, 2014 at 2:49 PM, Jason Bailey <Ja...@sas.com> wrote:
> Our team has been working on adopting the sling model paradigm which has given me a chance to get involved with creating a number of injectors to meet our needs.
>
> I wanted to share some of my observations.
> To clarify the existing flow, when we are looking at performing an injection it is really a three step process.
>
> * Obtain the base Object from the supplied adaptable
> * Retrieve a value from the base Object
> * Manipulate the value to meet the type expectations of the annotated
> field
>
> During any one of these steps, the result of the step could equal null and the process would then move on to the next injector.
> While working on these injectors I came across a series of commonalities within their implementations. You could actually define a shorthand for the majority of the injectors, and extrapolating from that you could define an injector completely in annotations.
>
> Example annotations derived from existing
>
> For defining the base object
> @adaptable(Class<?>) The expected object to be used as the source of
> this injector
> @via(String) use a JavaBean property of the adaptable as the source
> of the injection
> @viaArgument(Object*) if present, use this as the argument for the
> @via access
> @adaptTo(Class<?>) if present attempts to adapt the current base
> Object
>
> For retrieving the value
> @method(String) name of the method to use.
> @named(String) name of the argument for the above @method, defaults to
> the property name
> @default(Object*) replaces the returned value if null.
>
> Which, if you put the logic for these annotations in the framework, would allow you to do things like this mockup of a RequestAttribute injector:
>
> public class RequestAttributeInjector {
>
> @adaptable(ServletRequest.class) @method("getAttribute")
> Object getValue(Object adaptable, String name, Type declaredType, AnnotatedElement element, DisposalCallbackRegistry callbackRegistry){
> return adaptable;
> }
> }
>
> where a SlingBindings injector would look like this
>
> public class SlingBindingsInjector {
>
> @adaptable(ServletRequest.class) @via("attribute")
> @viaArgument(SlingBindings.class.name()) @method("get")
> Object getValue(Object adaptable, String name, Type declaredType, AnnotatedElement element, DisposalCallbackRegistry callbackRegistry){
> return adaptable;
> }
>
> }
>
> You'll notice I didn't touch step 3. Because once you extract these concepts into separate steps, type conversion is the same for all objects and therefore something that should be supplied by the framework. This sort of separation would provide consistency of type conversion across different injectors.
>
> My TL;DR comes down to
> I believe that by adding additional meta information to the sling injector definition, either through annotations or properties, that you could streamline the functionality and make it easier to develop new injectors. An example being the declaration of what type of adaptable is expected and verifying that prior to calling the injector.
>
> The addition of the meta information could be added expanded to the point that most injectors could be defined as a series of properties.
>
> Additionally, unifying the type conversion outside of the injector would be beneficial, as it provides a consistent type conversion process and enables improvements of that process to be shared by all injectors.
>
> WDYT?
>
> -Jason
Re: Some thoughts on Sling Models and injectors
Posted by Justin Edelson <ju...@justinedelson.com>.
Hi Jason,
I'm all for anything that could make injector development simpler. But
personally, that looks like a weird usage for annotations. Have you
considered using the Builder pattern?
Maybe an interface like this:
public interface InjectorFactory {
Injector build(InjectorBuilder builder);
}
And then you would have:
@Component @Service
public class RequestAttributeInjectorFactory {
public Injector build(InjectorBuilder builder) {
return builder.withName("request-attributes").withAdaptable(ServletRequest.class).withMethod("getAttribute").build();
}
}
This, IMHO, accomplishes the same goal, but somehow looks less
"magical" :) WDYT?
As I wrote yesterday in a separate thread, I do think there's been a
bit of sloppiness on my part about type checking/conversion. Unless
specifically required (i.e. the case of the ValueMapInjector), all
type checking and conversion should happen in the ModelAdapterFactory.
And really all conversion should happen via adaptTo().
Justin
On Thu, Aug 28, 2014 at 2:49 PM, Jason Bailey <Ja...@sas.com> wrote:
> Our team has been working on adopting the sling model paradigm which has given me a chance to get involved with creating a number of injectors to meet our needs.
>
> I wanted to share some of my observations.
> To clarify the existing flow, when we are looking at performing an injection it is really a three step process.
>
> * Obtain the base Object from the supplied adaptable
> * Retrieve a value from the base Object
> * Manipulate the value to meet the type expectations of the annotated field
>
> During any one of these steps, the result of the step could equal null and the process would then move on to the next injector.
> While working on these injectors I came across a series of commonalities within their implementations. You could actually define a shorthand for the majority of the injectors, and extrapolating from that you could define an injector completely in annotations.
>
> Example annotations derived from existing
>
> For defining the base object
> @adaptable(Class<?>) The expected object to be used as the source of this injector
> @via(String) use a JavaBean property of the adaptable as the source of the injection
> @viaArgument(Object*) if present, use this as the argument for the @via access
> @adaptTo(Class<?>) if present attempts to adapt the current base Object
>
> For retrieving the value
> @method(String) name of the method to use.
> @named(String) name of the argument for the above @method, defaults to the property name
> @default(Object*) replaces the returned value if null.
>
> Which, if you put the logic for these annotations in the framework, would allow you to do things like this mockup of a RequestAttribute injector:
>
> public class RequestAttributeInjector {
>
> @adaptable(ServletRequest.class) @method("getAttribute")
> Object getValue(Object adaptable, String name, Type declaredType, AnnotatedElement element, DisposalCallbackRegistry callbackRegistry){
> return adaptable;
> }
> }
>
> where a SlingBindings injector would look like this
>
> public class SlingBindingsInjector {
>
> @adaptable(ServletRequest.class) @via("attribute")
> @viaArgument(SlingBindings.class.name()) @method("get")
> Object getValue(Object adaptable, String name, Type declaredType, AnnotatedElement element, DisposalCallbackRegistry callbackRegistry){
> return adaptable;
> }
>
> }
>
> You'll notice I didn't touch step 3. Because once you extract these concepts into separate steps, type conversion is the same for all objects and therefore something that should be supplied by the framework. This sort of separation would provide consistency of type conversion across different injectors.
>
> My TL;DR comes down to
> I believe that by adding additional meta information to the sling injector definition, either through annotations or properties, that you could streamline the functionality and make it easier to develop new injectors. An example being the declaration of what type of adaptable is expected and verifying that prior to calling the injector.
>
> The addition of the meta information could be added expanded to the point that most injectors could be defined as a series of properties.
>
> Additionally, unifying the type conversion outside of the injector would be beneficial, as it provides a consistent type conversion process and enables improvements of that process to be shared by all injectors.
>
> WDYT?
>
> -Jason